Import Tint changes from Dawn

Changes:
  - 8f80d999c4738aef4f8d6664468e411c6b96df26 tint: Support multiple extensions per 'enable' by Ben Clayton <bclayton@google.com>
  - ed28de3c53937fd6c0824f534b66cdcea7c437a7 tint: Support @diagnostic on for loops by James Price <jrprice@google.com>
  - 9536ced0148a1c645f595326201200bb1fbe6975 tint: Support @diagnostic on while statements by James Price <jrprice@google.com>
  - d0ed63eb64f7761eaf9414fbbbe2a0e1f13e3f7a tint: Support @diagnostic on switch statements by James Price <jrprice@google.com>
  - 0607796ef8e26124299df34abf5f5a5a8803425e tint: Support @diagnostic on if statements by James Price <jrprice@google.com>
  - a370186824298735bac70a710815ae32f00a213f tint: Fix compilation on latest MSVC by Antonio Maiorano <amaiorano@google.com>
  - 1f6ce67ed28881b9a4a42713474120224eb6871d Move `clamp_frag_depth` transformer to backend. by dan sinclair <dsinclair@chromium.org>
  - 6521a357f5a54e1ed6296170beb1d8a90a015111 Convert generators over to use external texture options. by dan sinclair <dsinclair@chromium.org>
  - 97382d96f5b21e17be317ca048b69dfc11be33ce tint: Validate no template args with functions / builtin ... by Ben Clayton <bclayton@google.com>
  - 6784a5276639cda54003d15656de5c10b103030d Sync keyword/reserved word list. by dan sinclair <dsinclair@chromium.org>
  - 7bca4d72938a96abd493438e8ca0e1423ea5f20b tint: Remove constant::Value::AllEqual() by James Price <jrprice@google.com>
  - 69253ee8a527594ffc64732b608abbddb6075273 tint/uniformity: Move recently added tests by James Price <jrprice@google.com>
  - 8cb5ed8c2c48ec856b7c83f06ea5f9b077dc8230 tint/uniformity: Fix compound assignment LHS eval by James Price <jrprice@google.com>
  - 79d0f2b78435ab8e1b2e393d1e22274c249a5e24 [ir] Add `ir::Builtin` by dan sinclair <dsinclair@chromium.org>
  - cd097f40a3bf40b32324ba58913066dc5be95c18 tint: Improve module scope var diagnostic by Ben Clayton <bclayton@google.com>
  - e95b59c34dcabb38db618f25e2e9137ca77306ed Add ExternalTextureOptions by dan sinclair <dsinclair@chromium.org>
  - a36fa42dd73ce65491030331d4961a7af9de14f9 tint/cmd: Use correct preprocessor guard for libspriv.hpp by Ben Clayton <bclayton@google.com>
  - 574b4b19964714444836fa5c391a265266dcfdcb tint: Fix constant::Splat conversion of struct types by Ben Clayton <bclayton@google.com>
  - 7972a10aa8f3686c9b9d7f69fc9b18d9db53d3e7 tint: Add missing include by Ben Clayton <bclayton@google.com>
  - cc3f851ad8f2e15f917a04f8543310a6c06af2c7 tint/transform: Polyfill f32 conversion to i32 or u32 by Ben Clayton <bclayton@google.com>
  - f1f56b2d3ca8201ec7e9c2cbcabb565655a5ba15 tint/writer/glsl: Set default precision to highp by Ben Clayton <bclayton@google.com>
  - 4fea9d0fbbcd1a88cf59e8b39ff7064a16e46a19 tint: Implement Switch() without recursion by Ben Clayton <bclayton@google.com>
  - 23946b36060b87a622a93e0331777c171b95d643 tint: Move Switch() to own header by Ben Clayton <bclayton@google.com>
  - 9543f74739118a853dd5e5a46297f5442c3352f8 Move sem::BuiltinType to builtin::Function. by dan sinclair <dsinclair@chromium.org>
  - 03ee23ea9efa2c4d053cb2c23152fc23553a3eaf [ir] Convert materialized nodes to constants by dan sinclair <dsinclair@chromium.org>
  - 2657b923c91b7e8a6259d80895dbae1d9f6c58d8 tint/writer/glsl: Fix vector `select` by Ben Clayton <bclayton@google.com>
  - da353b4b391e1ed4527ca43f9e36ab2b3c463f1d tint: Flip evaluation order of assignment statements by Ben Clayton <bclayton@google.com>
  - 43c5efa7e813dab3b7991cdbc901dce4c639b64f [ir] Add error helper to ir builder. by dan sinclair <dsinclair@chromium.org>
  - 724a70f7a19862250bef6355203b024dbd431e35 [ir] Add user functions, value constructors and value con... by dan sinclair <dsinclair@chromium.org>
  - 51be3420b89f36ce3a9e27306dc0743915e2153b tint/writer/glsl: Fix emission of lowest i32 value by Ben Clayton <bclayton@google.com>
  - ce2578bc993371e473fe6f9801a2c287a5f8ad64 tint/transform: Refactor BuiltinPolyfill transform by Ben Clayton <bclayton@google.com>
  - eebbdef81a2a112126c6eb66e21be9dd3c5d5b1e Split `BindingPoint` out of MultiplanarExternalTexture. by dan sinclair <dsinclair@chromium.org>
  - 068eb3ebdaf66b47264392bfbc708f871d0b45ee tint: Fix HLSL texture queries and re-add textureNumLayer... by Ben Clayton <bclayton@google.com>
  - 0917fbb1472a7ffcbf614b63b1ee8dca1758ffa5 Reland "Add writer to emit the AST." by dan sinclair <dsinclair@chromium.org>
  - 2fe7f19026088e7cd0d1510f44fbcd4aef925c21 Use `absl::from_chars` instead of `strtod` by dan sinclair <dsinclair@chromium.org>
  - 64196ad60adfc7f831485ac7b592b8cd5f865a55 Add array length options header to `tint.h`. by dan sinclair <dsinclair@chromium.org>
GitOrigin-RevId: 8f80d999c4738aef4f8d6664468e411c6b96df26
Change-Id: I05826385293df952c7c1bbc5b8703614ea5a7cdd
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/123080
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/.gitignore b/.gitignore
index 70384df..b6cec0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 .DS_Store
 .gclient
 .gclient_entries
+.gclient_previous_sync_commits
 .vs
 .vscode/*
 !.vscode/tasks.json
@@ -10,10 +11,12 @@
 default.profraw
 lcov.info
 
-/buildtools
+/build/
+/buildtools/
 /cmake-build-*/
 /out
 /testing
+/third_party/abseil-cpp
 /third_party/benchmark
 /third_party/binutils
 /third_party/catapult
@@ -28,5 +31,4 @@
 /tools/clang
 /tools/bin
 
-/build*/
 /test.wgsl
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 615a71b..a2130e3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,6 +77,7 @@
 option_if_not_defined(TINT_BUILD_MSL_WRITER "Build the MSL output writer" ON)
 option_if_not_defined(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" ON)
 option_if_not_defined(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON)
+option_if_not_defined(TINT_BUILD_SYNTAX_TREE_WRITER "Build the syntax tree writer" OFF)
 
 option_if_not_defined(TINT_BUILD_IR "Build the IR" ON)
 
@@ -113,6 +114,7 @@
 message(STATUS "Tint build MSL writer: ${TINT_BUILD_MSL_WRITER}")
 message(STATUS "Tint build SPIR-V writer: ${TINT_BUILD_SPV_WRITER}")
 message(STATUS "Tint build WGSL writer: ${TINT_BUILD_WGSL_WRITER}")
+message(STATUS "Tint build Syntax Tree writer: ${TINT_BUILD_SYNTAX_TREE_WRITER}")
 message(STATUS "Tint build IR: ${TINT_BUILD_IR}")
 message(STATUS "Tint build fuzzers: ${TINT_BUILD_FUZZERS}")
 message(STATUS "Tint build SPIRV-Tools fuzzer: ${TINT_BUILD_SPIRV_TOOLS_FUZZER}")
@@ -284,6 +286,7 @@
   target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_MSL_WRITER=$<BOOL:${TINT_BUILD_MSL_WRITER}>)
   target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_SPV_WRITER=$<BOOL:${TINT_BUILD_SPV_WRITER}>)
   target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_WGSL_WRITER=$<BOOL:${TINT_BUILD_WGSL_WRITER}>)
+  target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_SYNTAX_TREE_WRITER=$<BOOL:${TINT_BUILD_SYNTAX_TREE_WRITER}>)
   target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_IR=$<BOOL:${TINT_BUILD_IR}>)
 
   if (COMPILER_IS_LIKE_GNU)
diff --git a/DEPS b/DEPS
index 007996b..3c1a058 100644
--- a/DEPS
+++ b/DEPS
@@ -85,6 +85,10 @@
     'dep_type': 'cipd',
   },
 
+  'third_party/abseil-cpp': {
+    'url': '{chromium_git}/chromium/src/third_party/abseil-cpp@bc3ab29356a081d0b5dd4ac55e30f4b45d8794cc',
+  },
+
   # Dependencies required for testing
   'testing': {
     'url': '{chromium_git}/chromium/src/testing@d5ea1bf4b64781cfe38f207f56f264eb080d06b2',
diff --git a/build_overrides/dawn.gni b/build_overrides/dawn.gni
new file mode 100644
index 0000000..6440625
--- /dev/null
+++ b/build_overrides/dawn.gni
@@ -0,0 +1,43 @@
+# Copyright 2018 The Dawn 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.
+
+# These are variables that are overridable by projects that include Dawn.
+# The values in this file are the defaults for when we are building from
+# Dawn's repository.
+
+# Whether we are building from Dawn's repository.
+# MUST be unset in other projects (will default to false).
+dawn_standalone = true
+
+# True if Dawn can access build/, testing/ and other Chrome folders.
+dawn_has_build = true
+
+# Defaults for these are set again in dawn_overrides_with_defaults.gni so that
+# users of Dawn don't have to set dirs if they happen to use the same as Dawn.
+
+# The paths to Dawn's dependencies
+dawn_abseil_dir = "//third_party/abseil-cpp"
+dawn_angle_dir = "//third_party/angle"
+dawn_jinja2_dir = "//third_party/jinja2"
+dawn_glfw_dir = "//third_party/glfw"
+dawn_googletest_dir = "//third_party/googletest"
+dawn_spirv_tools_dir = "//third_party/vulkan-deps/spirv-tools/src"
+dawn_swiftshader_dir = "//third_party/swiftshader"
+dawn_vulkan_loader_dir = "//third_party/vulkan-deps/vulkan-loader/src"
+dawn_vulkan_validation_layers_dir =
+    "//third_party/vulkan-deps/vulkan-validation-layers/src"
+
+# Optional path to a one-liner version file. Default is empty path indicating
+# that git should be used to figure out the version.
+dawn_version_file = ""
diff --git a/include/tint/tint.h b/include/tint/tint.h
index 758e62a..0a0c027 100644
--- a/include/tint/tint.h
+++ b/include/tint/tint.h
@@ -27,7 +27,6 @@
 #include "src/tint/reader/reader.h"
 #include "src/tint/text/unicode.h"
 #include "src/tint/transform/binding_remapper.h"
-#include "src/tint/transform/clamp_frag_depth.h"
 #include "src/tint/transform/first_index_offset.h"
 #include "src/tint/transform/manager.h"
 #include "src/tint/transform/multiplanar_external_texture.h"
@@ -36,6 +35,8 @@
 #include "src/tint/transform/substitute_override.h"
 #include "src/tint/transform/vertex_pulling.h"
 #include "src/tint/type/manager.h"
+#include "src/tint/writer/array_length_from_uniform_options.h"
+#include "src/tint/writer/binding_point.h"
 #include "src/tint/writer/flatten_bindings.h"
 #include "src/tint/writer/writer.h"
 
diff --git a/scripts/dawn_overrides_with_defaults.gni b/scripts/dawn_overrides_with_defaults.gni
new file mode 100644
index 0000000..bbe79e1
--- /dev/null
+++ b/scripts/dawn_overrides_with_defaults.gni
@@ -0,0 +1,91 @@
+# Copyright 2018 The Dawn 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.
+
+# This files imports the overrides for Dawn but sets the defaults so that
+# projects including Dawn don't have to set dirs if they happen to use the
+# same.
+# It takes advantage of GN's variable scoping rules to define global variables
+# inside if constructs.
+
+import("//build_overrides/dawn.gni")
+
+if (!defined(dawn_standalone)) {
+  dawn_standalone = false
+}
+
+if (!defined(dawn_has_build)) {
+  dawn_has_build = true
+}
+
+if (!defined(dawn_root)) {
+  dawn_root = get_path_info("..", "abspath")
+}
+dawn_gen_root = get_path_info("${dawn_root}", "gen_dir")
+
+if (!defined(dawn_jinja2_dir)) {
+  dawn_jinja2_dir = "//third_party/jinja2"
+}
+
+if (!defined(dawn_glfw_dir)) {
+  dawn_glfw_dir = "//third_party/glfw"
+}
+
+if (!defined(dawn_googletest_dir)) {
+  dawn_googletest_dir = "//third_party/googletest"
+}
+
+if (!defined(dawn_spirv_tools_dir)) {
+  dawn_spirv_tools_dir = "//third_party/vulkan-deps/spirv-tools/src"
+}
+
+if (!defined(dawn_swiftshader_dir)) {
+  # Default to swiftshader not being available.
+  dawn_swiftshader_dir = ""
+}
+
+if (!defined(dawn_vulkan_deps_dir)) {
+  dawn_vulkan_deps_dir = "//third_party/vulkan-deps"
+  if (dawn_standalone) {
+    dawn_vulkan_deps_dir = "${dawn_root}/third_party/vulkan-deps"
+  }
+}
+
+if (!defined(dawn_vulkan_headers_dir)) {
+  dawn_vulkan_headers_dir = "${dawn_vulkan_deps_dir}/vulkan-headers/src"
+}
+
+if (!defined(dawn_vulkan_loader_dir)) {
+  # Default to the Vulkan loader not being available except in standalone.
+  dawn_vulkan_loader_dir = ""
+  if (dawn_standalone) {
+    dawn_vulkan_loader_dir = "//third_party/vulkan-deps/vulkan-loader/src"
+  }
+}
+
+if (!defined(dawn_vulkan_tools_dir)) {
+  dawn_vulkan_tools_dir = "${dawn_vulkan_deps_dir}/vulkan-tools/src"
+}
+
+if (!defined(dawn_vulkan_validation_layers_dir)) {
+  # Default to VVLs not being available.
+  dawn_vulkan_validation_layers_dir = ""
+}
+
+if (!defined(dawn_abseil_dir)) {
+  dawn_abseil_dir = "//third_party/abseil-cpp"
+}
+
+if (!defined(dawn_version_file)) {
+  dawn_version_file = ""
+}
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 89eb6ee..11d98bd 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -14,6 +14,7 @@
 
 import("//build_overrides/build.gni")
 
+import("../../scripts/dawn_overrides_with_defaults.gni")
 import("../../tint_overrides_with_defaults.gni")
 
 if (tint_build_unittests) {
@@ -78,6 +79,12 @@
     defines += [ "TINT_BUILD_GLSL_WRITER=0" ]
   }
 
+  if (tint_build_syntax_tree_writer) {
+    defines += [ "TINT_BUILD_SYNTAX_TREE_WRITER=1" ]
+  } else {
+    defines += [ "TINT_BUILD_SYNTAX_TREE_WRITER=0" ]
+  }
+
   include_dirs = [
     "${tint_root_dir}/",
     "${tint_root_dir}/include/",
@@ -92,6 +99,17 @@
   }
 }
 
+group("abseil") {
+  # When build_with_chromium=true we need to include "//third_party/abseil-cpp:absl" while
+  # it's beneficial to be more specific with standalone Dawn, especially when it comes to
+  # including it as a dependency in other projects (such as Skia).
+  if (build_with_chromium) {
+    public_deps = [ "$dawn_abseil_dir:absl" ]
+  } else {
+    public_deps = [ "${dawn_root}/third_party/gn/abseil-cpp:strings" ]
+  }
+}
+
 ###############################################################################
 # Helper library for IO operations
 # Only to be used by tests and sample executable
@@ -195,6 +213,7 @@
     "scope_stack.h",
     "source.cc",
     "source.h",
+    "switch.h",
     "symbol.cc",
     "symbol.h",
     "symbol_table.cc",
@@ -476,6 +495,7 @@
     "ast/discard_statement.h",
     "ast/enable.h",
     "ast/expression.h",
+    "ast/extension.h",
     "ast/float_literal_expression.h",
     "ast/for_loop_statement.h",
     "ast/function.h",
@@ -562,6 +582,7 @@
     "ast/discard_statement.cc",
     "ast/enable.cc",
     "ast/expression.cc",
+    "ast/extension.cc",
     "ast/float_literal_expression.cc",
     "ast/for_loop_statement.cc",
     "ast/function.cc",
@@ -633,8 +654,6 @@
     "sem/builtin.h",
     "sem/builtin_enum_expression.cc",
     "sem/builtin_enum_expression.h",
-    "sem/builtin_type.cc",
-    "sem/builtin_type.h",
     "sem/call.cc",
     "sem/call.h",
     "sem/call_target.cc",
@@ -642,6 +661,7 @@
     "sem/evaluation_stage.h",
     "sem/expression.cc",
     "sem/expression.h",
+    "sem/external_texture.h",
     "sem/for_loop_statement.cc",
     "sem/for_loop_statement.h",
     "sem/function.cc",
@@ -718,6 +738,8 @@
     "builtin/diagnostic_severity.h",
     "builtin/extension.cc",
     "builtin/extension.h",
+    "builtin/function.cc",
+    "builtin/function.h",
     "builtin/interpolation_sampling.cc",
     "builtin/interpolation_sampling.h",
     "builtin/interpolation_type.cc",
@@ -877,14 +899,15 @@
     "writer/append_vector.h",
     "writer/array_length_from_uniform_options.cc",
     "writer/array_length_from_uniform_options.h",
+    "writer/binding_point.h",
     "writer/check_supported_extensions.cc",
     "writer/check_supported_extensions.h",
+    "writer/external_texture_options.cc",
+    "writer/external_texture_options.h",
     "writer/flatten_bindings.cc",
     "writer/flatten_bindings.h",
     "writer/float_to_string.cc",
     "writer/float_to_string.h",
-    "writer/generate_external_texture_bindings.cc",
-    "writer/generate_external_texture_bindings.h",
     "writer/text.cc",
     "writer/text.h",
     "writer/text_generator.cc",
@@ -953,6 +976,7 @@
   ]
 
   deps = [
+    ":abseil",
     ":libtint_ast_src",
     ":libtint_base_src",
     ":libtint_builtins_src",
@@ -1045,6 +1069,25 @@
   ]
 }
 
+libtint_source_set("libtint_syntax_tree_writer_src") {
+  sources = [
+    "writer/syntax_tree/generator.cc",
+    "writer/syntax_tree/generator.h",
+    "writer/syntax_tree/generator_impl.cc",
+    "writer/syntax_tree/generator_impl.h",
+  ]
+
+  deps = [
+    ":libtint_ast_src",
+    ":libtint_base_src",
+    ":libtint_builtins_src",
+    ":libtint_program_src",
+    ":libtint_sem_src",
+    ":libtint_type_src",
+    ":libtint_writer_src",
+  ]
+}
+
 source_set("libtint") {
   public_deps = [
     ":libtint_ast_src",
@@ -1089,6 +1132,10 @@
     public_deps += [ ":libtint_glsl_writer_src" ]
   }
 
+  if (tint_build_syntax_tree_writer) {
+    public_deps += [ ":libtint_syntax_tree_writer_src" ]
+  }
+
   configs += [ ":tint_common_config" ]
   public_configs = [ ":tint_public_config" ]
 
@@ -1580,7 +1627,6 @@
       "writer/check_supported_extensions_test.cc",
       "writer/flatten_bindings_test.cc",
       "writer/float_to_string_test.cc",
-      "writer/generate_external_texture_bindings_test.cc",
       "writer/text_generator_test.cc",
     ]
     deps = [
@@ -1590,6 +1636,16 @@
     ]
   }
 
+  # Note, this includes the source files along with the test as otherwise the cmd helpers wouldn't
+  # be included in the binary.
+  tint_unittests_source_set("tint_unittests_cmd_src") {
+    sources = [
+      "cmd/generate_external_texture_bindings.cc",
+      "cmd/generate_external_texture_bindings.h",
+      "cmd/generate_external_texture_bindings_test.cc",
+    ]
+  }
+
   tint_unittests_source_set("tint_unittests_spv_reader_src") {
     sources = [
       "reader/spirv/enum_converter_test.cc",
@@ -1939,6 +1995,7 @@
       "reflection_test.cc",
       "scope_stack_test.cc",
       "source_test.cc",
+      "switch_test.cc",
       "symbol_table_test.cc",
       "symbol_test.cc",
       "traits_test.cc",
@@ -1985,6 +2042,7 @@
       ":tint_unittests_ast_src",
       ":tint_unittests_base_src",
       ":tint_unittests_builtins_src",
+      ":tint_unittests_cmd_src",
       ":tint_unittests_constant_src",
       ":tint_unittests_core_src",
       ":tint_unittests_demangler_src",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 1d31896..07f7aa8 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -134,6 +134,8 @@
   ast/enable.h
   ast/expression.cc
   ast/expression.h
+  ast/extension.cc
+  ast/extension.h
   ast/float_literal_expression.cc
   ast/float_literal_expression.h
   ast/for_loop_statement.cc
@@ -280,6 +282,7 @@
   resolver/validator.cc
   resolver/validator.h
   scope_stack.h
+  switch.h
   sem/array_count.cc
   sem/array_count.h
   sem/behavior.cc
@@ -300,6 +303,7 @@
   sem/evaluation_stage.h
   sem/expression.cc
   sem/expression.h
+  sem/external_texture.h
   sem/for_loop_statement.cc
   sem/for_loop_statement.h
   sem/function_expression.cc
@@ -539,14 +543,15 @@
   writer/append_vector.h
   writer/array_length_from_uniform_options.cc
   writer/array_length_from_uniform_options.h
+  writer/binding_point.h
   writer/check_supported_extensions.cc
   writer/check_supported_extensions.h
+  writer/external_texture_options.cc
+  writer/external_texture_options.h
   writer/flatten_bindings.cc
   writer/flatten_bindings.h
   writer/float_to_string.cc
   writer/float_to_string.h
-  writer/generate_external_texture_bindings.cc
-  writer/generate_external_texture_bindings.h
   writer/text_generator.cc
   writer/text_generator.h
   writer/text.cc
@@ -563,12 +568,12 @@
 tint_generated(builtin/diagnostic_rule BENCH TEST)
 tint_generated(builtin/diagnostic_severity BENCH TEST)
 tint_generated(builtin/extension BENCH TEST)
+tint_generated(builtin/function)
 tint_generated(builtin/interpolation_sampling BENCH TEST)
 tint_generated(builtin/interpolation_type BENCH TEST)
 tint_generated(builtin/texel_format BENCH TEST)
 
 tint_generated(resolver/ctor_conv_intrinsic)
-tint_generated(sem/builtin_type)
 tint_generated(sem/parameter_usage)
 
 if(UNIX)
@@ -677,6 +682,15 @@
   )
 endif()
 
+if(${TINT_BUILD_SYNTAX_TREE_WRITER})
+    list(APPEND TINT_LIB_SRCS
+      writer/syntax_tree/generator.cc
+      writer/syntax_tree/generator.h
+      writer/syntax_tree/generator_impl.cc
+      writer/syntax_tree/generator_impl.h
+    )
+endif()
+
 if(${TINT_BUILD_IR})
   list(APPEND TINT_LIB_SRCS
     ir/binary.cc
@@ -689,8 +703,16 @@
     ir/builder.h
     ir/builder_impl.cc
     ir/builder_impl.h
+    ir/builtin.cc
+    ir/builtin.h
+    ir/call.cc
+    ir/call.h
     ir/constant.cc
     ir/constant.h
+    ir/construct.cc
+    ir/construct.h
+    ir/convert.cc
+    ir/convert.h
     ir/debug.cc
     ir/debug.h
     ir/disassembler.cc
@@ -713,6 +735,8 @@
     ir/temp.h
     ir/terminator.cc
     ir/terminator.h
+    ir/user_call.cc
+    ir/user_call.h
     ir/value.cc
     ir/value.h
   )
@@ -760,7 +784,7 @@
 ## Tint library
 add_library(libtint ${TINT_LIB_SRCS})
 tint_default_compile_options(libtint)
-target_link_libraries(libtint tint_diagnostic_utils)
+target_link_libraries(libtint tint_diagnostic_utils absl_strings)
 if (${TINT_SYMBOL_STORE_DEBUG_NAME})
     target_compile_definitions(libtint PUBLIC "TINT_SYMBOL_STORE_DEBUG_NAME=1")
 endif()
@@ -770,7 +794,7 @@
   # Tint library with fuzzer instrumentation
   add_library(libtint-fuzz ${TINT_LIB_SRCS})
   tint_default_compile_options(libtint-fuzz)
-  target_link_libraries(libtint-fuzz tint_diagnostic_utils)
+  target_link_libraries(libtint-fuzz tint_diagnostic_utils absl_strings)
   if (${COMPILER_IS_LIKE_GNU})
     target_compile_options(libtint-fuzz PRIVATE -fvisibility=hidden)
   endif()
@@ -942,6 +966,7 @@
     sem/struct_test.cc
     sem/value_expression_test.cc
     source_test.cc
+    switch_test.cc
     symbol_table_test.cc
     symbol_test.cc
     test_main.cc
@@ -997,10 +1022,17 @@
     writer/check_supported_extensions_test.cc
     writer/flatten_bindings_test.cc
     writer/float_to_string_test.cc
-    writer/generate_external_texture_bindings_test.cc
     writer/text_generator_test.cc
   )
 
+  # Noet, the source files are included here otherwise the cmd sources would not be included in the
+  # test binary.
+  list(APPEND TINT_TEST_SRCS
+    cmd/generate_external_texture_bindings.cc
+    cmd/generate_external_texture_bindings.h
+    cmd/generate_external_texture_bindings_test.cc
+  )
+
   # Uniformity analysis tests depend on WGSL reader
   if(${TINT_BUILD_WGSL_READER})
     list(APPEND TINT_TEST_SRCS
@@ -1437,7 +1469,7 @@
   endif()
 
   list(APPEND TINT_BENCHMARK_SRCS
-    "castable_bench.cc"
+    "switch_bench.cc"
     "bench/benchmark.cc"
     "reader/wgsl/parser_bench.cc"
   )
diff --git a/src/tint/ast/builtin_texture_helper_test.cc b/src/tint/ast/builtin_texture_helper_test.cc
index 6d6aa69..658b650 100644
--- a/src/tint/ast/builtin_texture_helper_test.cc
+++ b/src/tint/ast/builtin_texture_helper_test.cc
@@ -852,6 +852,17 @@
             /* returns value */ true,
         },
         {
+            ValidTextureOverload::kNumLayersCubeArray,
+            "textureNumLayers(t : texture_cube_array<f32>) -> u32",
+            TextureKind::kRegular,
+            type::SamplerKind::kSampler,
+            type::TextureDimension::kCubeArray,
+            TextureDataType::kF32,
+            "textureNumLayers",
+            [](ProgramBuilder* b) { return b->ExprList(kTextureName); },
+            /* returns value */ true,
+        },
+        {
             ValidTextureOverload::kNumLayersDepth2dArray,
             "textureNumLayers(t : texture_depth_2d_array) -> u32",
             TextureKind::kDepth,
@@ -863,6 +874,17 @@
             /* returns value */ true,
         },
         {
+            ValidTextureOverload::kNumLayersDepthCubeArray,
+            "textureNumLayers(t : texture_depth_cube_array) -> u32",
+            TextureKind::kDepth,
+            type::SamplerKind::kSampler,
+            type::TextureDimension::kCubeArray,
+            TextureDataType::kF32,
+            "textureNumLayers",
+            [](ProgramBuilder* b) { return b->ExprList(kTextureName); },
+            /* returns value */ true,
+        },
+        {
             ValidTextureOverload::kNumLayersStorageWO2dArray,
             "textureNumLayers(t : texture_storage_2d_array<rgba32float>) -> u32",
             tint::builtin::Access::kWrite,
diff --git a/src/tint/ast/builtin_texture_helper_test.h b/src/tint/ast/builtin_texture_helper_test.h
index 4e1e46f..738db0c 100644
--- a/src/tint/ast/builtin_texture_helper_test.h
+++ b/src/tint/ast/builtin_texture_helper_test.h
@@ -83,7 +83,9 @@
     kGatherCompareDepthCubeF32,
     kGatherCompareDepthCubeArrayF32,
     kNumLayers2dArray,
+    kNumLayersCubeArray,
     kNumLayersDepth2dArray,
+    kNumLayersDepthCubeArray,
     kNumLayersStorageWO2dArray,
     kNumLevels2d,
     kNumLevels2dArray,
diff --git a/src/tint/ast/enable.cc b/src/tint/ast/enable.cc
index 6087a3e..bb2b3b6 100644
--- a/src/tint/ast/enable.cc
+++ b/src/tint/ast/enable.cc
@@ -20,15 +20,29 @@
 
 namespace tint::ast {
 
-Enable::Enable(ProgramID pid, NodeID nid, const Source& src, builtin::Extension ext)
-    : Base(pid, nid, src), extension(ext) {}
+Enable::Enable(ProgramID pid,
+               NodeID nid,
+               const Source& src,
+               utils::VectorRef<const Extension*> exts)
+    : Base(pid, nid, src), extensions(std::move(exts)) {}
 
 Enable::Enable(Enable&&) = default;
 
 Enable::~Enable() = default;
 
+bool Enable::HasExtension(builtin::Extension ext) const {
+    for (auto* e : extensions) {
+        if (e->name == ext) {
+            return true;
+        }
+    }
+    return false;
+}
+
 const Enable* Enable::Clone(CloneContext* ctx) const {
     auto src = ctx->Clone(source);
-    return ctx->dst->create<Enable>(src, extension);
+    auto exts = ctx->Clone(extensions);
+    return ctx->dst->create<Enable>(src, std::move(exts));
 }
+
 }  // namespace tint::ast
diff --git a/src/tint/ast/enable.h b/src/tint/ast/enable.h
index 0c64df9..d87d12f 100644
--- a/src/tint/ast/enable.h
+++ b/src/tint/ast/enable.h
@@ -19,8 +19,7 @@
 #include <utility>
 #include <vector>
 
-#include "src/tint/ast/node.h"
-#include "src/tint/builtin/extension.h"
+#include "src/tint/ast/extension.h"
 
 namespace tint::ast {
 
@@ -35,21 +34,24 @@
     /// @param pid the identifier of the program that owns this node
     /// @param nid the unique node identifier
     /// @param src the source of this node
-    /// @param ext the extension
-    Enable(ProgramID pid, NodeID nid, const Source& src, builtin::Extension ext);
+    /// @param exts the extensions being enabled by this directive
+    Enable(ProgramID pid, NodeID nid, const Source& src, utils::VectorRef<const Extension*> exts);
     /// Move constructor
     Enable(Enable&&);
 
     ~Enable() override;
 
-    /// Clones this node and all transitive child nodes using the `CloneContext`
-    /// `ctx`.
+    /// @param ext the extension to search for
+    /// @returns true if this Enable lists the given extension
+    bool HasExtension(builtin::Extension ext) const;
+
+    /// Clones this node and all transitive child nodes using the `CloneContext` `ctx`.
     /// @param ctx the clone context
     /// @return the newly cloned node
     const Enable* Clone(CloneContext* ctx) const override;
 
-    /// The extension name
-    const builtin::Extension extension;
+    /// The extensions being enabled by this directive
+    const utils::Vector<const Extension*, 4> extensions;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/enable_test.cc b/src/tint/ast/enable_test.cc
index 5622207..20105bc 100644
--- a/src/tint/ast/enable_test.cc
+++ b/src/tint/ast/enable_test.cc
@@ -27,7 +27,14 @@
     EXPECT_EQ(ext->source.range.begin.column, 2u);
     EXPECT_EQ(ext->source.range.end.line, 20u);
     EXPECT_EQ(ext->source.range.end.column, 5u);
-    EXPECT_EQ(ext->extension, builtin::Extension::kF16);
+    ASSERT_EQ(ext->extensions.Length(), 1u);
+    EXPECT_EQ(ext->extensions[0]->name, builtin::Extension::kF16);
+}
+
+TEST_F(EnableTest, HasExtension) {
+    auto* ext = Enable(Source{{{20, 2}, {20, 5}}}, builtin::Extension::kF16);
+    EXPECT_TRUE(ext->HasExtension(builtin::Extension::kF16));
+    EXPECT_FALSE(ext->HasExtension(builtin::Extension::kChromiumDisableUniformityAnalysis));
 }
 
 }  // namespace
diff --git a/src/tint/ast/extension.cc b/src/tint/ast/extension.cc
new file mode 100644
index 0000000..a45c704
--- /dev/null
+++ b/src/tint/ast/extension.cc
@@ -0,0 +1,40 @@
+// Copyright 2023 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/tint/ast/extension.h"
+
+#include "src/tint/program_builder.h"
+
+//! @cond Doxygen_Suppress
+// Doxygen gets confused with tint::ast::Extension and tint::builtin::Extension
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::Extension);
+
+namespace tint::ast {
+
+Extension::Extension(ProgramID pid, NodeID nid, const Source& src, builtin::Extension ext)
+    : Base(pid, nid, src), name(ext) {}
+
+Extension::Extension(Extension&&) = default;
+
+Extension::~Extension() = default;
+
+const Extension* Extension::Clone(CloneContext* ctx) const {
+    auto src = ctx->Clone(source);
+    return ctx->dst->create<Extension>(src, name);
+}
+
+}  // namespace tint::ast
+
+//! @endcond
diff --git a/src/tint/ast/extension.h b/src/tint/ast/extension.h
new file mode 100644
index 0000000..93f3baa
--- /dev/null
+++ b/src/tint/ast/extension.h
@@ -0,0 +1,52 @@
+// Copyright 2023 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_TINT_AST_EXTENSION_H_
+#define SRC_TINT_AST_EXTENSION_H_
+
+#include "src/tint/ast/node.h"
+#include "src/tint/builtin/extension.h"
+
+namespace tint::ast {
+
+/// An extension used in an "enable" directive. Example:
+/// ```
+///   enable f16;
+/// ```
+class Extension final : public Castable<Extension, Node> {
+  public:
+    /// Create a extension
+    /// @param pid the identifier of the program that owns this node
+    /// @param nid the unique node identifier
+    /// @param src the source of this node
+    /// @param ext the extension
+    Extension(ProgramID pid, NodeID nid, const Source& src, builtin::Extension ext);
+    /// Move constructor
+    Extension(Extension&&);
+
+    ~Extension() override;
+
+    /// Clones this node and all transitive child nodes using the `CloneContext`
+    /// `ctx`.
+    /// @param ctx the clone context
+    /// @return the newly cloned node
+    const Extension* Clone(CloneContext* ctx) const override;
+
+    /// The extension name
+    const builtin::Extension name;
+};
+
+}  // namespace tint::ast
+
+#endif  // SRC_TINT_AST_EXTENSION_H_
diff --git a/src/tint/ast/for_loop_statement.cc b/src/tint/ast/for_loop_statement.cc
index aba956d..b2a5470 100644
--- a/src/tint/ast/for_loop_statement.cc
+++ b/src/tint/ast/for_loop_statement.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/for_loop_statement.h"
 
+#include <utility>
+
 #include "src/tint/program_builder.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::ForLoopStatement);
@@ -26,14 +28,24 @@
                                    const Statement* init,
                                    const Expression* cond,
                                    const Statement* cont,
-                                   const BlockStatement* b)
-    : Base(pid, nid, src), initializer(init), condition(cond), continuing(cont), body(b) {
+                                   const BlockStatement* b,
+                                   utils::VectorRef<const ast::Attribute*> attrs)
+    : Base(pid, nid, src),
+      initializer(init),
+      condition(cond),
+      continuing(cont),
+      body(b),
+      attributes(std::move(attrs)) {
     TINT_ASSERT(AST, body);
 
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, initializer, program_id);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, continuing, program_id);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
+    for (auto* attr : attributes) {
+        TINT_ASSERT(AST, attr);
+        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
+    }
 }
 
 ForLoopStatement::ForLoopStatement(ForLoopStatement&&) = default;
@@ -48,7 +60,8 @@
     auto* cond = ctx->Clone(condition);
     auto* cont = ctx->Clone(continuing);
     auto* b = ctx->Clone(body);
-    return ctx->dst->create<ForLoopStatement>(src, init, cond, cont, b);
+    auto attrs = ctx->Clone(attributes);
+    return ctx->dst->create<ForLoopStatement>(src, init, cond, cont, b, std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/for_loop_statement.h b/src/tint/ast/for_loop_statement.h
index 59e0587..6063dda 100644
--- a/src/tint/ast/for_loop_statement.h
+++ b/src/tint/ast/for_loop_statement.h
@@ -32,13 +32,15 @@
     /// @param condition the optional loop condition expression
     /// @param continuing the optional continuing statement
     /// @param body the loop body
+    /// @param attributes the while statement attributes
     ForLoopStatement(ProgramID pid,
                      NodeID nid,
                      const Source& source,
                      const Statement* initializer,
                      const Expression* condition,
                      const Statement* continuing,
-                     const BlockStatement* body);
+                     const BlockStatement* body,
+                     utils::VectorRef<const ast::Attribute*> attributes);
     /// Move constructor
     ForLoopStatement(ForLoopStatement&&);
     ~ForLoopStatement() override;
@@ -60,6 +62,9 @@
 
     /// The loop body block
     const BlockStatement* const body;
+
+    /// The attribute list
+    const utils::Vector<const Attribute*, 1> attributes;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/for_loop_statement_test.cc b/src/tint/ast/for_loop_statement_test.cc
index 5e2a000..ad9050e 100644
--- a/src/tint/ast/for_loop_statement_test.cc
+++ b/src/tint/ast/for_loop_statement_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ast/binary_expression.h"
 #include "src/tint/ast/test_helper.h"
@@ -50,6 +51,15 @@
     EXPECT_EQ(l->body, body);
 }
 
+TEST_F(ForLoopStatementTest, Creation_WithAttributes) {
+    auto* attr1 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "foo");
+    auto* attr2 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "bar");
+    auto* body = Block(Return());
+    auto* l = For(nullptr, nullptr, nullptr, body, utils::Vector{attr1, attr2});
+
+    EXPECT_THAT(l->attributes, testing::ElementsAre(attr1, attr2));
+}
+
 TEST_F(ForLoopStatementTest, Assert_Null_Body) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ast/if_statement.cc b/src/tint/ast/if_statement.cc
index fdea1da..6878c9f 100644
--- a/src/tint/ast/if_statement.cc
+++ b/src/tint/ast/if_statement.cc
@@ -25,8 +25,13 @@
                          const Source& src,
                          const Expression* cond,
                          const BlockStatement* b,
-                         const Statement* else_stmt)
-    : Base(pid, nid, src), condition(cond), body(b), else_statement(else_stmt) {
+                         const Statement* else_stmt,
+                         utils::VectorRef<const Attribute*> attrs)
+    : Base(pid, nid, src),
+      condition(cond),
+      body(b),
+      else_statement(else_stmt),
+      attributes(std::move(attrs)) {
     TINT_ASSERT(AST, condition);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
     TINT_ASSERT(AST, body);
@@ -35,6 +40,10 @@
         TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, else_statement, program_id);
         TINT_ASSERT(AST, (else_statement->IsAnyOf<IfStatement, BlockStatement>()));
     }
+    for (auto* attr : attributes) {
+        TINT_ASSERT(AST, attr);
+        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
+    }
 }
 
 IfStatement::IfStatement(IfStatement&&) = default;
@@ -47,7 +56,8 @@
     auto* cond = ctx->Clone(condition);
     auto* b = ctx->Clone(body);
     auto* el = ctx->Clone(else_statement);
-    return ctx->dst->create<IfStatement>(src, cond, b, el);
+    auto attrs = ctx->Clone(attributes);
+    return ctx->dst->create<IfStatement>(src, cond, b, el, std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/if_statement.h b/src/tint/ast/if_statement.h
index 1c1abc3..8f291a8 100644
--- a/src/tint/ast/if_statement.h
+++ b/src/tint/ast/if_statement.h
@@ -32,12 +32,14 @@
     /// @param condition the if condition
     /// @param body the if body
     /// @param else_stmt the else statement, or nullptr
+    /// @param attributes the if statement attributes
     IfStatement(ProgramID pid,
                 NodeID nid,
                 const Source& src,
                 const Expression* condition,
                 const BlockStatement* body,
-                const Statement* else_stmt);
+                const Statement* else_stmt,
+                utils::VectorRef<const Attribute*> attributes);
     /// Move constructor
     IfStatement(IfStatement&&);
     ~IfStatement() override;
@@ -56,6 +58,9 @@
 
     /// The optional else statement, or nullptr
     const Statement* const else_statement;
+
+    /// The attribute list
+    const utils::Vector<const Attribute*, 1> attributes;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/if_statement_test.cc b/src/tint/ast/if_statement_test.cc
index 9115cb7..b5b85fe 100644
--- a/src/tint/ast/if_statement_test.cc
+++ b/src/tint/ast/if_statement_test.cc
@@ -14,6 +14,7 @@
 
 #include "src/tint/ast/if_statement.h"
 
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ast/discard_statement.h"
 #include "src/tint/ast/test_helper.h"
@@ -31,6 +32,15 @@
     EXPECT_EQ(src.range.begin.column, 2u);
 }
 
+TEST_F(IfStatementTest, Creation_WithAttributes) {
+    auto* attr1 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "foo");
+    auto* attr2 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "bar");
+    auto* cond = Expr("cond");
+    auto* stmt = If(cond, Block(), ElseStmt(), utils::Vector{attr1, attr2});
+
+    EXPECT_THAT(stmt->attributes, testing::ElementsAre(attr1, attr2));
+}
+
 TEST_F(IfStatementTest, IsIf) {
     auto* stmt = If(Expr(true), Block());
     EXPECT_TRUE(stmt->Is<IfStatement>());
diff --git a/src/tint/ast/module.cc b/src/tint/ast/module.cc
index 32dafe8..fa652de 100644
--- a/src/tint/ast/module.cc
+++ b/src/tint/ast/module.cc
@@ -18,6 +18,7 @@
 
 #include "src/tint/ast/type_decl.h"
 #include "src/tint/program_builder.h"
+#include "src/tint/switch.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::Module);
 
diff --git a/src/tint/ast/switch_statement.cc b/src/tint/ast/switch_statement.cc
index 1cdf8a4..0445e48 100644
--- a/src/tint/ast/switch_statement.cc
+++ b/src/tint/ast/switch_statement.cc
@@ -26,14 +26,19 @@
                                  NodeID nid,
                                  const Source& src,
                                  const Expression* cond,
-                                 utils::VectorRef<const CaseStatement*> b)
-    : Base(pid, nid, src), condition(cond), body(std::move(b)) {
+                                 utils::VectorRef<const CaseStatement*> b,
+                                 utils::VectorRef<const Attribute*> attrs)
+    : Base(pid, nid, src), condition(cond), body(std::move(b)), attributes(std::move(attrs)) {
     TINT_ASSERT(AST, condition);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
     for (auto* stmt : body) {
         TINT_ASSERT(AST, stmt);
         TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, stmt, program_id);
     }
+    for (auto* attr : attributes) {
+        TINT_ASSERT(AST, attr);
+        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
+    }
 }
 
 SwitchStatement::SwitchStatement(SwitchStatement&&) = default;
@@ -45,7 +50,8 @@
     auto src = ctx->Clone(source);
     auto* cond = ctx->Clone(condition);
     auto b = ctx->Clone(body);
-    return ctx->dst->create<SwitchStatement>(src, cond, std::move(b));
+    auto attrs = ctx->Clone(attributes);
+    return ctx->dst->create<SwitchStatement>(src, cond, std::move(b), std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/switch_statement.h b/src/tint/ast/switch_statement.h
index eb083f0..e918535 100644
--- a/src/tint/ast/switch_statement.h
+++ b/src/tint/ast/switch_statement.h
@@ -29,11 +29,13 @@
     /// @param src the source of this node
     /// @param condition the switch condition
     /// @param body the switch body
+    /// @param attributes the switch statement attributes
     SwitchStatement(ProgramID pid,
                     NodeID nid,
                     const Source& src,
                     const Expression* condition,
-                    utils::VectorRef<const CaseStatement*> body);
+                    utils::VectorRef<const CaseStatement*> body,
+                    utils::VectorRef<const Attribute*> attributes);
     /// Move constructor
     SwitchStatement(SwitchStatement&&);
     ~SwitchStatement() override;
@@ -50,6 +52,9 @@
     /// The Switch body
     const utils::Vector<const CaseStatement*, 4> body;
     SwitchStatement(const SwitchStatement&) = delete;
+
+    /// The attribute list
+    const utils::Vector<const Attribute*, 1> attributes;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/switch_statement_test.cc b/src/tint/ast/switch_statement_test.cc
index e6101c8..aab2604 100644
--- a/src/tint/ast/switch_statement_test.cc
+++ b/src/tint/ast/switch_statement_test.cc
@@ -14,6 +14,7 @@
 
 #include "src/tint/ast/switch_statement.h"
 
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ast/test_helper.h"
 
@@ -29,7 +30,7 @@
     auto* ident = Expr("ident");
     utils::Vector body{case_stmt};
 
-    auto* stmt = create<SwitchStatement>(ident, body);
+    auto* stmt = create<SwitchStatement>(ident, body, utils::Empty);
     EXPECT_EQ(stmt->condition, ident);
     ASSERT_EQ(stmt->body.Length(), 1u);
     EXPECT_EQ(stmt->body[0], case_stmt);
@@ -37,18 +38,28 @@
 
 TEST_F(SwitchStatementTest, Creation_WithSource) {
     auto* ident = Expr("ident");
-    auto* stmt = create<SwitchStatement>(Source{Source::Location{20, 2}}, ident, utils::Empty);
+    auto* stmt =
+        create<SwitchStatement>(Source{Source::Location{20, 2}}, ident, utils::Empty, utils::Empty);
     auto src = stmt->source;
     EXPECT_EQ(src.range.begin.line, 20u);
     EXPECT_EQ(src.range.begin.column, 2u);
 }
 
+TEST_F(SwitchStatementTest, Creation_WithAttributes) {
+    auto* attr1 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "foo");
+    auto* attr2 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "bar");
+    auto* ident = Expr("ident");
+    auto* stmt = create<SwitchStatement>(ident, utils::Empty, utils::Vector{attr1, attr2});
+
+    EXPECT_THAT(stmt->attributes, testing::ElementsAre(attr1, attr2));
+}
+
 TEST_F(SwitchStatementTest, IsSwitch) {
     utils::Vector lit{CaseSelector(2_i)};
     auto* ident = Expr("ident");
     utils::Vector body{create<CaseStatement>(lit, Block())};
 
-    auto* stmt = create<SwitchStatement>(ident, body);
+    auto* stmt = create<SwitchStatement>(ident, body, utils::Empty);
     EXPECT_TRUE(stmt->Is<SwitchStatement>());
 }
 
@@ -60,7 +71,7 @@
             CaseStatementList cases;
             cases.Push(
                 b.create<CaseStatement>(utils::Vector{b.CaseSelector(b.Expr(1_i))}, b.Block()));
-            b.create<SwitchStatement>(nullptr, cases);
+            b.create<SwitchStatement>(nullptr, cases, utils::Empty);
         },
         "internal compiler error");
 }
@@ -70,7 +81,7 @@
     EXPECT_FATAL_FAILURE(
         {
             ProgramBuilder b;
-            b.create<SwitchStatement>(b.Expr(true), CaseStatementList{nullptr});
+            b.create<SwitchStatement>(b.Expr(true), CaseStatementList{nullptr}, utils::Empty);
         },
         "internal compiler error");
 }
@@ -80,13 +91,15 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<SwitchStatement>(b2.Expr(true), utils::Vector{
-                                                          b1.create<CaseStatement>(
-                                                              utils::Vector{
-                                                                  b1.CaseSelector(b1.Expr(1_i)),
-                                                              },
-                                                              b1.Block()),
-                                                      });
+            b1.create<SwitchStatement>(b2.Expr(true),
+                                       utils::Vector{
+                                           b1.create<CaseStatement>(
+                                               utils::Vector{
+                                                   b1.CaseSelector(b1.Expr(1_i)),
+                                               },
+                                               b1.Block()),
+                                       },
+                                       utils::Empty);
         },
         "internal compiler error");
 }
@@ -96,13 +109,15 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<SwitchStatement>(b1.Expr(true), utils::Vector{
-                                                          b2.create<CaseStatement>(
-                                                              utils::Vector{
-                                                                  b2.CaseSelector(b2.Expr(1_i)),
-                                                              },
-                                                              b2.Block()),
-                                                      });
+            b1.create<SwitchStatement>(b1.Expr(true),
+                                       utils::Vector{
+                                           b2.create<CaseStatement>(
+                                               utils::Vector{
+                                                   b2.CaseSelector(b2.Expr(1_i)),
+                                               },
+                                               b2.Block()),
+                                       },
+                                       utils::Empty);
         },
         "internal compiler error");
 }
diff --git a/src/tint/ast/traverse_expressions.h b/src/tint/ast/traverse_expressions.h
index fb8aad1..84a10c3 100644
--- a/src/tint/ast/traverse_expressions.h
+++ b/src/tint/ast/traverse_expressions.h
@@ -25,6 +25,7 @@
 #include "src/tint/ast/member_accessor_expression.h"
 #include "src/tint/ast/phony_expression.h"
 #include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/switch.h"
 #include "src/tint/utils/compiler_macros.h"
 #include "src/tint/utils/reverse.h"
 #include "src/tint/utils/vector.h"
diff --git a/src/tint/ast/while_statement.cc b/src/tint/ast/while_statement.cc
index 160af4b..47cf2bb 100644
--- a/src/tint/ast/while_statement.cc
+++ b/src/tint/ast/while_statement.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ast/while_statement.h"
 
+#include <utility>
+
 #include "src/tint/program_builder.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::WhileStatement);
@@ -24,13 +26,18 @@
                                NodeID nid,
                                const Source& src,
                                const Expression* cond,
-                               const BlockStatement* b)
-    : Base(pid, nid, src), condition(cond), body(b) {
+                               const BlockStatement* b,
+                               utils::VectorRef<const ast::Attribute*> attrs)
+    : Base(pid, nid, src), condition(cond), body(b), attributes(std::move(attrs)) {
     TINT_ASSERT(AST, cond);
     TINT_ASSERT(AST, body);
 
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
+    for (auto* attr : attributes) {
+        TINT_ASSERT(AST, attr);
+        TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
+    }
 }
 
 WhileStatement::WhileStatement(WhileStatement&&) = default;
@@ -43,7 +50,8 @@
 
     auto* cond = ctx->Clone(condition);
     auto* b = ctx->Clone(body);
-    return ctx->dst->create<WhileStatement>(src, cond, b);
+    auto attrs = ctx->Clone(attributes);
+    return ctx->dst->create<WhileStatement>(src, cond, b, std::move(attrs));
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/while_statement.h b/src/tint/ast/while_statement.h
index 4e8dd7e..b2b91dc 100644
--- a/src/tint/ast/while_statement.h
+++ b/src/tint/ast/while_statement.h
@@ -30,11 +30,13 @@
     /// @param source the for loop statement source
     /// @param condition the optional loop condition expression
     /// @param body the loop body
+    /// @param attributes the while statement attributes
     WhileStatement(ProgramID pid,
                    NodeID nid,
                    const Source& source,
                    const Expression* condition,
-                   const BlockStatement* body);
+                   const BlockStatement* body,
+                   utils::VectorRef<const ast::Attribute*> attributes);
     /// Move constructor
     WhileStatement(WhileStatement&&);
     ~WhileStatement() override;
@@ -50,6 +52,9 @@
 
     /// The loop body block
     const BlockStatement* const body;
+
+    /// The attribute list
+    const utils::Vector<const Attribute*, 1> attributes;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/while_statement_test.cc b/src/tint/ast/while_statement_test.cc
index 73c9e56..ef287fc 100644
--- a/src/tint/ast/while_statement_test.cc
+++ b/src/tint/ast/while_statement_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "gmock/gmock.h"
 #include "gtest/gtest-spi.h"
 #include "src/tint/ast/binary_expression.h"
 #include "src/tint/ast/test_helper.h"
@@ -41,6 +42,16 @@
     EXPECT_EQ(src.range.begin.column, 2u);
 }
 
+TEST_F(WhileStatementTest, Creation_WithAttributes) {
+    auto* attr1 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "foo");
+    auto* attr2 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "bar");
+    auto* cond = create<BinaryExpression>(BinaryOp::kLessThan, Expr("i"), Expr(5_u));
+    auto* body = Block(Return());
+    auto* l = While(cond, body, utils::Vector{attr1, attr2});
+
+    EXPECT_THAT(l->attributes, testing::ElementsAre(attr1, attr2));
+}
+
 TEST_F(WhileStatementTest, Assert_Null_Cond) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/builtin/function.cc b/src/tint/builtin/function.cc
new file mode 100644
index 0000000..88e86b6
--- /dev/null
+++ b/src/tint/builtin/function.cc
@@ -0,0 +1,614 @@
+// Copyright 2023 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/builtin/function.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/builtin/function.h"
+
+namespace tint::builtin {
+
+Function ParseFunction(const std::string& name) {
+    if (name == "abs") {
+        return Function::kAbs;
+    }
+    if (name == "acos") {
+        return Function::kAcos;
+    }
+    if (name == "acosh") {
+        return Function::kAcosh;
+    }
+    if (name == "all") {
+        return Function::kAll;
+    }
+    if (name == "any") {
+        return Function::kAny;
+    }
+    if (name == "arrayLength") {
+        return Function::kArrayLength;
+    }
+    if (name == "asin") {
+        return Function::kAsin;
+    }
+    if (name == "asinh") {
+        return Function::kAsinh;
+    }
+    if (name == "atan") {
+        return Function::kAtan;
+    }
+    if (name == "atan2") {
+        return Function::kAtan2;
+    }
+    if (name == "atanh") {
+        return Function::kAtanh;
+    }
+    if (name == "ceil") {
+        return Function::kCeil;
+    }
+    if (name == "clamp") {
+        return Function::kClamp;
+    }
+    if (name == "cos") {
+        return Function::kCos;
+    }
+    if (name == "cosh") {
+        return Function::kCosh;
+    }
+    if (name == "countLeadingZeros") {
+        return Function::kCountLeadingZeros;
+    }
+    if (name == "countOneBits") {
+        return Function::kCountOneBits;
+    }
+    if (name == "countTrailingZeros") {
+        return Function::kCountTrailingZeros;
+    }
+    if (name == "cross") {
+        return Function::kCross;
+    }
+    if (name == "degrees") {
+        return Function::kDegrees;
+    }
+    if (name == "determinant") {
+        return Function::kDeterminant;
+    }
+    if (name == "distance") {
+        return Function::kDistance;
+    }
+    if (name == "dot") {
+        return Function::kDot;
+    }
+    if (name == "dot4I8Packed") {
+        return Function::kDot4I8Packed;
+    }
+    if (name == "dot4U8Packed") {
+        return Function::kDot4U8Packed;
+    }
+    if (name == "dpdx") {
+        return Function::kDpdx;
+    }
+    if (name == "dpdxCoarse") {
+        return Function::kDpdxCoarse;
+    }
+    if (name == "dpdxFine") {
+        return Function::kDpdxFine;
+    }
+    if (name == "dpdy") {
+        return Function::kDpdy;
+    }
+    if (name == "dpdyCoarse") {
+        return Function::kDpdyCoarse;
+    }
+    if (name == "dpdyFine") {
+        return Function::kDpdyFine;
+    }
+    if (name == "exp") {
+        return Function::kExp;
+    }
+    if (name == "exp2") {
+        return Function::kExp2;
+    }
+    if (name == "extractBits") {
+        return Function::kExtractBits;
+    }
+    if (name == "faceForward") {
+        return Function::kFaceForward;
+    }
+    if (name == "firstLeadingBit") {
+        return Function::kFirstLeadingBit;
+    }
+    if (name == "firstTrailingBit") {
+        return Function::kFirstTrailingBit;
+    }
+    if (name == "floor") {
+        return Function::kFloor;
+    }
+    if (name == "fma") {
+        return Function::kFma;
+    }
+    if (name == "fract") {
+        return Function::kFract;
+    }
+    if (name == "frexp") {
+        return Function::kFrexp;
+    }
+    if (name == "fwidth") {
+        return Function::kFwidth;
+    }
+    if (name == "fwidthCoarse") {
+        return Function::kFwidthCoarse;
+    }
+    if (name == "fwidthFine") {
+        return Function::kFwidthFine;
+    }
+    if (name == "insertBits") {
+        return Function::kInsertBits;
+    }
+    if (name == "inverseSqrt") {
+        return Function::kInverseSqrt;
+    }
+    if (name == "ldexp") {
+        return Function::kLdexp;
+    }
+    if (name == "length") {
+        return Function::kLength;
+    }
+    if (name == "log") {
+        return Function::kLog;
+    }
+    if (name == "log2") {
+        return Function::kLog2;
+    }
+    if (name == "max") {
+        return Function::kMax;
+    }
+    if (name == "min") {
+        return Function::kMin;
+    }
+    if (name == "mix") {
+        return Function::kMix;
+    }
+    if (name == "modf") {
+        return Function::kModf;
+    }
+    if (name == "normalize") {
+        return Function::kNormalize;
+    }
+    if (name == "pack2x16float") {
+        return Function::kPack2X16Float;
+    }
+    if (name == "pack2x16snorm") {
+        return Function::kPack2X16Snorm;
+    }
+    if (name == "pack2x16unorm") {
+        return Function::kPack2X16Unorm;
+    }
+    if (name == "pack4x8snorm") {
+        return Function::kPack4X8Snorm;
+    }
+    if (name == "pack4x8unorm") {
+        return Function::kPack4X8Unorm;
+    }
+    if (name == "pow") {
+        return Function::kPow;
+    }
+    if (name == "quantizeToF16") {
+        return Function::kQuantizeToF16;
+    }
+    if (name == "radians") {
+        return Function::kRadians;
+    }
+    if (name == "reflect") {
+        return Function::kReflect;
+    }
+    if (name == "refract") {
+        return Function::kRefract;
+    }
+    if (name == "reverseBits") {
+        return Function::kReverseBits;
+    }
+    if (name == "round") {
+        return Function::kRound;
+    }
+    if (name == "saturate") {
+        return Function::kSaturate;
+    }
+    if (name == "select") {
+        return Function::kSelect;
+    }
+    if (name == "sign") {
+        return Function::kSign;
+    }
+    if (name == "sin") {
+        return Function::kSin;
+    }
+    if (name == "sinh") {
+        return Function::kSinh;
+    }
+    if (name == "smoothstep") {
+        return Function::kSmoothstep;
+    }
+    if (name == "sqrt") {
+        return Function::kSqrt;
+    }
+    if (name == "step") {
+        return Function::kStep;
+    }
+    if (name == "storageBarrier") {
+        return Function::kStorageBarrier;
+    }
+    if (name == "tan") {
+        return Function::kTan;
+    }
+    if (name == "tanh") {
+        return Function::kTanh;
+    }
+    if (name == "transpose") {
+        return Function::kTranspose;
+    }
+    if (name == "trunc") {
+        return Function::kTrunc;
+    }
+    if (name == "unpack2x16float") {
+        return Function::kUnpack2X16Float;
+    }
+    if (name == "unpack2x16snorm") {
+        return Function::kUnpack2X16Snorm;
+    }
+    if (name == "unpack2x16unorm") {
+        return Function::kUnpack2X16Unorm;
+    }
+    if (name == "unpack4x8snorm") {
+        return Function::kUnpack4X8Snorm;
+    }
+    if (name == "unpack4x8unorm") {
+        return Function::kUnpack4X8Unorm;
+    }
+    if (name == "workgroupBarrier") {
+        return Function::kWorkgroupBarrier;
+    }
+    if (name == "workgroupUniformLoad") {
+        return Function::kWorkgroupUniformLoad;
+    }
+    if (name == "textureDimensions") {
+        return Function::kTextureDimensions;
+    }
+    if (name == "textureGather") {
+        return Function::kTextureGather;
+    }
+    if (name == "textureGatherCompare") {
+        return Function::kTextureGatherCompare;
+    }
+    if (name == "textureNumLayers") {
+        return Function::kTextureNumLayers;
+    }
+    if (name == "textureNumLevels") {
+        return Function::kTextureNumLevels;
+    }
+    if (name == "textureNumSamples") {
+        return Function::kTextureNumSamples;
+    }
+    if (name == "textureSample") {
+        return Function::kTextureSample;
+    }
+    if (name == "textureSampleBias") {
+        return Function::kTextureSampleBias;
+    }
+    if (name == "textureSampleCompare") {
+        return Function::kTextureSampleCompare;
+    }
+    if (name == "textureSampleCompareLevel") {
+        return Function::kTextureSampleCompareLevel;
+    }
+    if (name == "textureSampleGrad") {
+        return Function::kTextureSampleGrad;
+    }
+    if (name == "textureSampleLevel") {
+        return Function::kTextureSampleLevel;
+    }
+    if (name == "textureSampleBaseClampToEdge") {
+        return Function::kTextureSampleBaseClampToEdge;
+    }
+    if (name == "textureStore") {
+        return Function::kTextureStore;
+    }
+    if (name == "textureLoad") {
+        return Function::kTextureLoad;
+    }
+    if (name == "atomicLoad") {
+        return Function::kAtomicLoad;
+    }
+    if (name == "atomicStore") {
+        return Function::kAtomicStore;
+    }
+    if (name == "atomicAdd") {
+        return Function::kAtomicAdd;
+    }
+    if (name == "atomicSub") {
+        return Function::kAtomicSub;
+    }
+    if (name == "atomicMax") {
+        return Function::kAtomicMax;
+    }
+    if (name == "atomicMin") {
+        return Function::kAtomicMin;
+    }
+    if (name == "atomicAnd") {
+        return Function::kAtomicAnd;
+    }
+    if (name == "atomicOr") {
+        return Function::kAtomicOr;
+    }
+    if (name == "atomicXor") {
+        return Function::kAtomicXor;
+    }
+    if (name == "atomicExchange") {
+        return Function::kAtomicExchange;
+    }
+    if (name == "atomicCompareExchangeWeak") {
+        return Function::kAtomicCompareExchangeWeak;
+    }
+    if (name == "_tint_materialize") {
+        return Function::kTintMaterialize;
+    }
+    return Function::kNone;
+}
+
+const char* str(Function i) {
+    switch (i) {
+        case Function::kNone:
+            return "<none>";
+        case Function::kAbs:
+            return "abs";
+        case Function::kAcos:
+            return "acos";
+        case Function::kAcosh:
+            return "acosh";
+        case Function::kAll:
+            return "all";
+        case Function::kAny:
+            return "any";
+        case Function::kArrayLength:
+            return "arrayLength";
+        case Function::kAsin:
+            return "asin";
+        case Function::kAsinh:
+            return "asinh";
+        case Function::kAtan:
+            return "atan";
+        case Function::kAtan2:
+            return "atan2";
+        case Function::kAtanh:
+            return "atanh";
+        case Function::kCeil:
+            return "ceil";
+        case Function::kClamp:
+            return "clamp";
+        case Function::kCos:
+            return "cos";
+        case Function::kCosh:
+            return "cosh";
+        case Function::kCountLeadingZeros:
+            return "countLeadingZeros";
+        case Function::kCountOneBits:
+            return "countOneBits";
+        case Function::kCountTrailingZeros:
+            return "countTrailingZeros";
+        case Function::kCross:
+            return "cross";
+        case Function::kDegrees:
+            return "degrees";
+        case Function::kDeterminant:
+            return "determinant";
+        case Function::kDistance:
+            return "distance";
+        case Function::kDot:
+            return "dot";
+        case Function::kDot4I8Packed:
+            return "dot4I8Packed";
+        case Function::kDot4U8Packed:
+            return "dot4U8Packed";
+        case Function::kDpdx:
+            return "dpdx";
+        case Function::kDpdxCoarse:
+            return "dpdxCoarse";
+        case Function::kDpdxFine:
+            return "dpdxFine";
+        case Function::kDpdy:
+            return "dpdy";
+        case Function::kDpdyCoarse:
+            return "dpdyCoarse";
+        case Function::kDpdyFine:
+            return "dpdyFine";
+        case Function::kExp:
+            return "exp";
+        case Function::kExp2:
+            return "exp2";
+        case Function::kExtractBits:
+            return "extractBits";
+        case Function::kFaceForward:
+            return "faceForward";
+        case Function::kFirstLeadingBit:
+            return "firstLeadingBit";
+        case Function::kFirstTrailingBit:
+            return "firstTrailingBit";
+        case Function::kFloor:
+            return "floor";
+        case Function::kFma:
+            return "fma";
+        case Function::kFract:
+            return "fract";
+        case Function::kFrexp:
+            return "frexp";
+        case Function::kFwidth:
+            return "fwidth";
+        case Function::kFwidthCoarse:
+            return "fwidthCoarse";
+        case Function::kFwidthFine:
+            return "fwidthFine";
+        case Function::kInsertBits:
+            return "insertBits";
+        case Function::kInverseSqrt:
+            return "inverseSqrt";
+        case Function::kLdexp:
+            return "ldexp";
+        case Function::kLength:
+            return "length";
+        case Function::kLog:
+            return "log";
+        case Function::kLog2:
+            return "log2";
+        case Function::kMax:
+            return "max";
+        case Function::kMin:
+            return "min";
+        case Function::kMix:
+            return "mix";
+        case Function::kModf:
+            return "modf";
+        case Function::kNormalize:
+            return "normalize";
+        case Function::kPack2X16Float:
+            return "pack2x16float";
+        case Function::kPack2X16Snorm:
+            return "pack2x16snorm";
+        case Function::kPack2X16Unorm:
+            return "pack2x16unorm";
+        case Function::kPack4X8Snorm:
+            return "pack4x8snorm";
+        case Function::kPack4X8Unorm:
+            return "pack4x8unorm";
+        case Function::kPow:
+            return "pow";
+        case Function::kQuantizeToF16:
+            return "quantizeToF16";
+        case Function::kRadians:
+            return "radians";
+        case Function::kReflect:
+            return "reflect";
+        case Function::kRefract:
+            return "refract";
+        case Function::kReverseBits:
+            return "reverseBits";
+        case Function::kRound:
+            return "round";
+        case Function::kSaturate:
+            return "saturate";
+        case Function::kSelect:
+            return "select";
+        case Function::kSign:
+            return "sign";
+        case Function::kSin:
+            return "sin";
+        case Function::kSinh:
+            return "sinh";
+        case Function::kSmoothstep:
+            return "smoothstep";
+        case Function::kSqrt:
+            return "sqrt";
+        case Function::kStep:
+            return "step";
+        case Function::kStorageBarrier:
+            return "storageBarrier";
+        case Function::kTan:
+            return "tan";
+        case Function::kTanh:
+            return "tanh";
+        case Function::kTranspose:
+            return "transpose";
+        case Function::kTrunc:
+            return "trunc";
+        case Function::kUnpack2X16Float:
+            return "unpack2x16float";
+        case Function::kUnpack2X16Snorm:
+            return "unpack2x16snorm";
+        case Function::kUnpack2X16Unorm:
+            return "unpack2x16unorm";
+        case Function::kUnpack4X8Snorm:
+            return "unpack4x8snorm";
+        case Function::kUnpack4X8Unorm:
+            return "unpack4x8unorm";
+        case Function::kWorkgroupBarrier:
+            return "workgroupBarrier";
+        case Function::kWorkgroupUniformLoad:
+            return "workgroupUniformLoad";
+        case Function::kTextureDimensions:
+            return "textureDimensions";
+        case Function::kTextureGather:
+            return "textureGather";
+        case Function::kTextureGatherCompare:
+            return "textureGatherCompare";
+        case Function::kTextureNumLayers:
+            return "textureNumLayers";
+        case Function::kTextureNumLevels:
+            return "textureNumLevels";
+        case Function::kTextureNumSamples:
+            return "textureNumSamples";
+        case Function::kTextureSample:
+            return "textureSample";
+        case Function::kTextureSampleBias:
+            return "textureSampleBias";
+        case Function::kTextureSampleCompare:
+            return "textureSampleCompare";
+        case Function::kTextureSampleCompareLevel:
+            return "textureSampleCompareLevel";
+        case Function::kTextureSampleGrad:
+            return "textureSampleGrad";
+        case Function::kTextureSampleLevel:
+            return "textureSampleLevel";
+        case Function::kTextureSampleBaseClampToEdge:
+            return "textureSampleBaseClampToEdge";
+        case Function::kTextureStore:
+            return "textureStore";
+        case Function::kTextureLoad:
+            return "textureLoad";
+        case Function::kAtomicLoad:
+            return "atomicLoad";
+        case Function::kAtomicStore:
+            return "atomicStore";
+        case Function::kAtomicAdd:
+            return "atomicAdd";
+        case Function::kAtomicSub:
+            return "atomicSub";
+        case Function::kAtomicMax:
+            return "atomicMax";
+        case Function::kAtomicMin:
+            return "atomicMin";
+        case Function::kAtomicAnd:
+            return "atomicAnd";
+        case Function::kAtomicOr:
+            return "atomicOr";
+        case Function::kAtomicXor:
+            return "atomicXor";
+        case Function::kAtomicExchange:
+            return "atomicExchange";
+        case Function::kAtomicCompareExchangeWeak:
+            return "atomicCompareExchangeWeak";
+        case Function::kTintMaterialize:
+            return "_tint_materialize";
+    }
+    return "<unknown>";
+}
+
+utils::StringStream& operator<<(utils::StringStream& out, Function i) {
+    out << str(i);
+    return out;
+}
+
+}  // namespace tint::builtin
diff --git a/src/tint/builtin/function.cc.tmpl b/src/tint/builtin/function.cc.tmpl
new file mode 100644
index 0000000..e2729dc
--- /dev/null
+++ b/src/tint/builtin/function.cc.tmpl
@@ -0,0 +1,44 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate function.cc
+
+To update the generated file, run:
+    ./tools/run gen
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+#include "src/tint/builtin/function.h"
+
+namespace tint::builtin {
+
+Function ParseFunction(const std::string& name) {
+{{- range Sem.Builtins  }}
+    if (name == "{{.Name}}") {
+        return Function::k{{PascalCase .Name}};
+    }
+{{- end  }}
+    return Function::kNone;
+}
+
+const char* str(Function i) {
+    switch (i) {
+        case Function::kNone:
+            return "<none>";
+{{- range Sem.Builtins  }}
+        case Function::k{{PascalCase .Name}}:
+            return "{{.Name}}";
+{{- end  }}
+    }
+    return "<unknown>";
+}
+
+utils::StringStream& operator<<(utils::StringStream& out, Function i) {
+    out << str(i);
+    return out;
+}
+
+}  // namespace tint::builtin
diff --git a/src/tint/builtin/function.h b/src/tint/builtin/function.h
new file mode 100644
index 0000000..3ccb341
--- /dev/null
+++ b/src/tint/builtin/function.h
@@ -0,0 +1,405 @@
+// Copyright 2023 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/builtin/function.h.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SRC_TINT_BUILTIN_FUNCTION_H_
+#define SRC_TINT_BUILTIN_FUNCTION_H_
+
+#include <string>
+
+#include "src/tint/utils/string_stream.h"
+
+// \cond DO_NOT_DOCUMENT
+namespace tint::builtin {
+
+/// Enumerator of all builtin functions
+enum class Function {
+    kNone = -1,
+    kAbs,
+    kAcos,
+    kAcosh,
+    kAll,
+    kAny,
+    kArrayLength,
+    kAsin,
+    kAsinh,
+    kAtan,
+    kAtan2,
+    kAtanh,
+    kCeil,
+    kClamp,
+    kCos,
+    kCosh,
+    kCountLeadingZeros,
+    kCountOneBits,
+    kCountTrailingZeros,
+    kCross,
+    kDegrees,
+    kDeterminant,
+    kDistance,
+    kDot,
+    kDot4I8Packed,
+    kDot4U8Packed,
+    kDpdx,
+    kDpdxCoarse,
+    kDpdxFine,
+    kDpdy,
+    kDpdyCoarse,
+    kDpdyFine,
+    kExp,
+    kExp2,
+    kExtractBits,
+    kFaceForward,
+    kFirstLeadingBit,
+    kFirstTrailingBit,
+    kFloor,
+    kFma,
+    kFract,
+    kFrexp,
+    kFwidth,
+    kFwidthCoarse,
+    kFwidthFine,
+    kInsertBits,
+    kInverseSqrt,
+    kLdexp,
+    kLength,
+    kLog,
+    kLog2,
+    kMax,
+    kMin,
+    kMix,
+    kModf,
+    kNormalize,
+    kPack2X16Float,
+    kPack2X16Snorm,
+    kPack2X16Unorm,
+    kPack4X8Snorm,
+    kPack4X8Unorm,
+    kPow,
+    kQuantizeToF16,
+    kRadians,
+    kReflect,
+    kRefract,
+    kReverseBits,
+    kRound,
+    kSaturate,
+    kSelect,
+    kSign,
+    kSin,
+    kSinh,
+    kSmoothstep,
+    kSqrt,
+    kStep,
+    kStorageBarrier,
+    kTan,
+    kTanh,
+    kTranspose,
+    kTrunc,
+    kUnpack2X16Float,
+    kUnpack2X16Snorm,
+    kUnpack2X16Unorm,
+    kUnpack4X8Snorm,
+    kUnpack4X8Unorm,
+    kWorkgroupBarrier,
+    kWorkgroupUniformLoad,
+    kTextureDimensions,
+    kTextureGather,
+    kTextureGatherCompare,
+    kTextureNumLayers,
+    kTextureNumLevels,
+    kTextureNumSamples,
+    kTextureSample,
+    kTextureSampleBias,
+    kTextureSampleCompare,
+    kTextureSampleCompareLevel,
+    kTextureSampleGrad,
+    kTextureSampleLevel,
+    kTextureSampleBaseClampToEdge,
+    kTextureStore,
+    kTextureLoad,
+    kAtomicLoad,
+    kAtomicStore,
+    kAtomicAdd,
+    kAtomicSub,
+    kAtomicMax,
+    kAtomicMin,
+    kAtomicAnd,
+    kAtomicOr,
+    kAtomicXor,
+    kAtomicExchange,
+    kAtomicCompareExchangeWeak,
+    kTintMaterialize,
+};
+
+/// Matches the Function by name
+/// @param name the builtin name to parse
+/// @returns the parsed Function, or Function::kNone if `name` did not
+/// match any builtin function.
+Function ParseFunction(const std::string& name);
+
+/// @returns the name of the builtin function type. The spelling, including
+/// case, matches the name in the WGSL spec.
+const char* str(Function i);
+
+/// Emits the name of the builtin function type. The spelling, including case,
+/// matches the name in the WGSL spec.
+utils::StringStream& operator<<(utils::StringStream& out, Function i);
+
+/// All builtin functions
+constexpr Function kFunctions[] = {
+    Function::kAbs,
+    Function::kAcos,
+    Function::kAcosh,
+    Function::kAll,
+    Function::kAny,
+    Function::kArrayLength,
+    Function::kAsin,
+    Function::kAsinh,
+    Function::kAtan,
+    Function::kAtan2,
+    Function::kAtanh,
+    Function::kCeil,
+    Function::kClamp,
+    Function::kCos,
+    Function::kCosh,
+    Function::kCountLeadingZeros,
+    Function::kCountOneBits,
+    Function::kCountTrailingZeros,
+    Function::kCross,
+    Function::kDegrees,
+    Function::kDeterminant,
+    Function::kDistance,
+    Function::kDot,
+    Function::kDot4I8Packed,
+    Function::kDot4U8Packed,
+    Function::kDpdx,
+    Function::kDpdxCoarse,
+    Function::kDpdxFine,
+    Function::kDpdy,
+    Function::kDpdyCoarse,
+    Function::kDpdyFine,
+    Function::kExp,
+    Function::kExp2,
+    Function::kExtractBits,
+    Function::kFaceForward,
+    Function::kFirstLeadingBit,
+    Function::kFirstTrailingBit,
+    Function::kFloor,
+    Function::kFma,
+    Function::kFract,
+    Function::kFrexp,
+    Function::kFwidth,
+    Function::kFwidthCoarse,
+    Function::kFwidthFine,
+    Function::kInsertBits,
+    Function::kInverseSqrt,
+    Function::kLdexp,
+    Function::kLength,
+    Function::kLog,
+    Function::kLog2,
+    Function::kMax,
+    Function::kMin,
+    Function::kMix,
+    Function::kModf,
+    Function::kNormalize,
+    Function::kPack2X16Float,
+    Function::kPack2X16Snorm,
+    Function::kPack2X16Unorm,
+    Function::kPack4X8Snorm,
+    Function::kPack4X8Unorm,
+    Function::kPow,
+    Function::kQuantizeToF16,
+    Function::kRadians,
+    Function::kReflect,
+    Function::kRefract,
+    Function::kReverseBits,
+    Function::kRound,
+    Function::kSaturate,
+    Function::kSelect,
+    Function::kSign,
+    Function::kSin,
+    Function::kSinh,
+    Function::kSmoothstep,
+    Function::kSqrt,
+    Function::kStep,
+    Function::kStorageBarrier,
+    Function::kTan,
+    Function::kTanh,
+    Function::kTranspose,
+    Function::kTrunc,
+    Function::kUnpack2X16Float,
+    Function::kUnpack2X16Snorm,
+    Function::kUnpack2X16Unorm,
+    Function::kUnpack4X8Snorm,
+    Function::kUnpack4X8Unorm,
+    Function::kWorkgroupBarrier,
+    Function::kWorkgroupUniformLoad,
+    Function::kTextureDimensions,
+    Function::kTextureGather,
+    Function::kTextureGatherCompare,
+    Function::kTextureNumLayers,
+    Function::kTextureNumLevels,
+    Function::kTextureNumSamples,
+    Function::kTextureSample,
+    Function::kTextureSampleBias,
+    Function::kTextureSampleCompare,
+    Function::kTextureSampleCompareLevel,
+    Function::kTextureSampleGrad,
+    Function::kTextureSampleLevel,
+    Function::kTextureSampleBaseClampToEdge,
+    Function::kTextureStore,
+    Function::kTextureLoad,
+    Function::kAtomicLoad,
+    Function::kAtomicStore,
+    Function::kAtomicAdd,
+    Function::kAtomicSub,
+    Function::kAtomicMax,
+    Function::kAtomicMin,
+    Function::kAtomicAnd,
+    Function::kAtomicOr,
+    Function::kAtomicXor,
+    Function::kAtomicExchange,
+    Function::kAtomicCompareExchangeWeak,
+    Function::kTintMaterialize,
+};
+
+/// All builtin function names
+constexpr const char* kFunctionStrings[] = {
+    "abs",
+    "acos",
+    "acosh",
+    "all",
+    "any",
+    "arrayLength",
+    "asin",
+    "asinh",
+    "atan",
+    "atan2",
+    "atanh",
+    "ceil",
+    "clamp",
+    "cos",
+    "cosh",
+    "countLeadingZeros",
+    "countOneBits",
+    "countTrailingZeros",
+    "cross",
+    "degrees",
+    "determinant",
+    "distance",
+    "dot",
+    "dot4I8Packed",
+    "dot4U8Packed",
+    "dpdx",
+    "dpdxCoarse",
+    "dpdxFine",
+    "dpdy",
+    "dpdyCoarse",
+    "dpdyFine",
+    "exp",
+    "exp2",
+    "extractBits",
+    "faceForward",
+    "firstLeadingBit",
+    "firstTrailingBit",
+    "floor",
+    "fma",
+    "fract",
+    "frexp",
+    "fwidth",
+    "fwidthCoarse",
+    "fwidthFine",
+    "insertBits",
+    "inverseSqrt",
+    "ldexp",
+    "length",
+    "log",
+    "log2",
+    "max",
+    "min",
+    "mix",
+    "modf",
+    "normalize",
+    "pack2x16float",
+    "pack2x16snorm",
+    "pack2x16unorm",
+    "pack4x8snorm",
+    "pack4x8unorm",
+    "pow",
+    "quantizeToF16",
+    "radians",
+    "reflect",
+    "refract",
+    "reverseBits",
+    "round",
+    "saturate",
+    "select",
+    "sign",
+    "sin",
+    "sinh",
+    "smoothstep",
+    "sqrt",
+    "step",
+    "storageBarrier",
+    "tan",
+    "tanh",
+    "transpose",
+    "trunc",
+    "unpack2x16float",
+    "unpack2x16snorm",
+    "unpack2x16unorm",
+    "unpack4x8snorm",
+    "unpack4x8unorm",
+    "workgroupBarrier",
+    "workgroupUniformLoad",
+    "textureDimensions",
+    "textureGather",
+    "textureGatherCompare",
+    "textureNumLayers",
+    "textureNumLevels",
+    "textureNumSamples",
+    "textureSample",
+    "textureSampleBias",
+    "textureSampleCompare",
+    "textureSampleCompareLevel",
+    "textureSampleGrad",
+    "textureSampleLevel",
+    "textureSampleBaseClampToEdge",
+    "textureStore",
+    "textureLoad",
+    "atomicLoad",
+    "atomicStore",
+    "atomicAdd",
+    "atomicSub",
+    "atomicMax",
+    "atomicMin",
+    "atomicAnd",
+    "atomicOr",
+    "atomicXor",
+    "atomicExchange",
+    "atomicCompareExchangeWeak",
+    "_tint_materialize",
+};
+
+}  // namespace tint::builtin
+// \endcond
+
+#endif  // SRC_TINT_BUILTIN_FUNCTION_H_
diff --git a/src/tint/builtin/function.h.tmpl b/src/tint/builtin/function.h.tmpl
new file mode 100644
index 0000000..d9109bf
--- /dev/null
+++ b/src/tint/builtin/function.h.tmpl
@@ -0,0 +1,63 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate function.h
+
+To update the generated file, run:
+    ./tools/run gen
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+#ifndef SRC_TINT_BUILTIN_FUNCTION_H_
+#define SRC_TINT_BUILTIN_FUNCTION_H_
+
+#include <string>
+
+#include "src/tint/utils/string_stream.h"
+
+// \cond DO_NOT_DOCUMENT
+namespace tint::builtin {
+
+/// Enumerator of all builtin functions
+enum class Function {
+    kNone = -1,
+{{- range Sem.Builtins }}
+    k{{PascalCase .Name}},
+{{- end }}
+};
+
+/// Matches the Function by name
+/// @param name the builtin name to parse
+/// @returns the parsed Function, or Function::kNone if `name` did not
+/// match any builtin function.
+Function ParseFunction(const std::string& name);
+
+/// @returns the name of the builtin function type. The spelling, including
+/// case, matches the name in the WGSL spec.
+const char* str(Function i);
+
+/// Emits the name of the builtin function type. The spelling, including case,
+/// matches the name in the WGSL spec.
+utils::StringStream& operator<<(utils::StringStream& out, Function i);
+
+/// All builtin functions
+constexpr Function kFunctions[] = {
+{{- range Sem.Builtins }}
+    Function::k{{PascalCase .Name}},
+{{- end }}
+};
+
+/// All builtin function names
+constexpr const char* kFunctionStrings[] = {
+{{- range Sem.Builtins }}
+    "{{.Name}}",
+{{- end }}
+};
+
+}  // namespace tint::builtin
+// \endcond
+
+#endif  // SRC_TINT_BUILTIN_FUNCTION_H_
diff --git a/src/tint/castable.h b/src/tint/castable.h
index d6597fc..71e3ab8 100644
--- a/src/tint/castable.h
+++ b/src/tint/castable.h
@@ -21,9 +21,7 @@
 #include <utility>
 
 #include "src/tint/traits.h"
-#include "src/tint/utils/bitcast.h"
 #include "src/tint/utils/crc32.h"
-#include "src/tint/utils/defer.h"
 
 #if defined(__clang__)
 /// Temporarily disable certain warnings when using Castable API
@@ -532,280 +530,6 @@
 template <typename... TYPES>
 using CastableCommonBase = detail::CastableCommonBase<TYPES...>;
 
-/// Default can be used as the default case for a Switch(), when all previous cases failed to match.
-///
-/// Example:
-/// ```
-/// Switch(object,
-///     [&](TypeA*) { /* ... */ },
-///     [&](TypeB*) { /* ... */ },
-///     [&](Default) { /* If not TypeA or TypeB */ });
-/// ```
-struct Default {};
-
-namespace detail {
-
-/// Evaluates to the Switch case type being matched by the switch case function `FN`.
-/// @note does not handle the Default case
-/// @see Switch().
-template <typename FN>
-using SwitchCaseType = std::remove_pointer_t<traits::ParameterType<std::remove_reference_t<FN>, 0>>;
-
-/// Evaluates to true if the function `FN` has the signature of a Default case in a Switch().
-/// @see Switch().
-template <typename FN>
-inline constexpr bool IsDefaultCase =
-    std::is_same_v<traits::ParameterType<std::remove_reference_t<FN>, 0>, Default>;
-
-/// Searches the list of Switch cases for a Default case, returning the index of the Default case.
-/// If the a Default case is not found in the tuple, then -1 is returned.
-template <typename TUPLE, std::size_t START_IDX = 0>
-constexpr int IndexOfDefaultCase() {
-    if constexpr (START_IDX < std::tuple_size_v<TUPLE>) {
-        return IsDefaultCase<std::tuple_element_t<START_IDX, TUPLE>>
-                   ? static_cast<int>(START_IDX)
-                   : IndexOfDefaultCase<TUPLE, START_IDX + 1>();
-    } else {
-        return -1;
-    }
-}
-
-/// The implementation of Switch() for non-Default cases.
-/// Switch splits the cases into two a low and high block of cases, and quickly rules out blocks
-/// that cannot match by comparing the HashCode of the object and the cases in the block. If a block
-/// of cases may match the given object's type, then that block is split into two, and the process
-/// recurses. When NonDefaultCases() is called with a single case, then As<> will be used to
-/// dynamically cast to the case type and if the cast succeeds, then the case handler is called.
-/// @returns true if a case handler was found, otherwise false.
-template <typename T, typename RETURN_TYPE, typename... CASES>
-inline bool NonDefaultCases([[maybe_unused]] T* object,
-                            const TypeInfo* type,
-                            [[maybe_unused]] RETURN_TYPE* result,
-                            std::tuple<CASES...>&& cases) {
-    using Cases = std::tuple<CASES...>;
-
-    static constexpr bool kHasReturnType = !std::is_same_v<RETURN_TYPE, void>;
-    static constexpr size_t kNumCases = sizeof...(CASES);
-
-    if constexpr (kNumCases == 0) {
-        // No cases. Nothing to do.
-        return false;
-    } else if constexpr (kNumCases == 1) {  // NOLINT: cpplint doesn't understand
-                                            // `else if constexpr`
-        // Single case.
-        using CaseFunc = std::tuple_element_t<0, Cases>;
-        static_assert(!IsDefaultCase<CaseFunc>, "NonDefaultCases called with a Default case");
-        // Attempt to dynamically cast the object to the handler type. If that succeeds, call the
-        // case handler with the cast object.
-        using CaseType = SwitchCaseType<CaseFunc>;
-        if (type->Is<CaseType>()) {
-            auto* ptr = static_cast<CaseType*>(object);
-            if constexpr (kHasReturnType) {
-                new (result) RETURN_TYPE(static_cast<RETURN_TYPE>(std::get<0>(cases)(ptr)));
-            } else {
-                std::get<0>(cases)(ptr);
-            }
-            return true;
-        }
-        return false;
-    } else {
-        // Multiple cases.
-        // Check the hashcode bits to see if there's any possibility of a case matching in these
-        // cases. If there isn't, we can skip all these cases.
-        if (MaybeAnyOf(TypeInfo::CombinedHashCodeOf<SwitchCaseType<CASES>...>(),
-                       type->full_hashcode)) {
-            // Split the cases into two, and recurse.
-            constexpr size_t kMid = kNumCases / 2;
-            return NonDefaultCases(object, type, result, traits::Slice<0, kMid>(cases)) ||
-                   NonDefaultCases(object, type, result,
-                                   traits::Slice<kMid, kNumCases - kMid>(cases));
-        } else {
-            return false;
-        }
-    }
-}
-
-/// The implementation of Switch() for all cases.
-/// @see NonDefaultCases
-template <typename T, typename RETURN_TYPE, typename... CASES>
-inline void SwitchCases(T* object, RETURN_TYPE* result, std::tuple<CASES...>&& cases) {
-    using Cases = std::tuple<CASES...>;
-
-    static constexpr int kDefaultIndex = detail::IndexOfDefaultCase<Cases>();
-    static constexpr bool kHasDefaultCase = kDefaultIndex >= 0;
-    static constexpr bool kHasReturnType = !std::is_same_v<RETURN_TYPE, void>;
-
-    // Static assertions
-    static constexpr bool kDefaultIsOK =
-        kDefaultIndex == -1 || kDefaultIndex == static_cast<int>(std::tuple_size_v<Cases> - 1);
-    static constexpr bool kReturnIsOK =
-        kHasDefaultCase || !kHasReturnType || std::is_constructible_v<RETURN_TYPE>;
-    static_assert(kDefaultIsOK, "Default case must be last in Switch()");
-    static_assert(kReturnIsOK,
-                  "Switch() requires either a Default case or a return type that is either void or "
-                  "default-constructable");
-
-    // If the static asserts have fired, don't bother spewing more errors below
-    static constexpr bool kAllOK = kDefaultIsOK && kReturnIsOK;
-    if constexpr (kAllOK) {
-        if (object) {
-            auto* type = &object->TypeInfo();
-            if constexpr (kHasDefaultCase) {
-                // Evaluate non-default cases.
-                if (!detail::NonDefaultCases<T>(object, type, result,
-                                                traits::Slice<0, kDefaultIndex>(cases))) {
-                    // Nothing matched. Evaluate default case.
-                    if constexpr (kHasReturnType) {
-                        new (result) RETURN_TYPE(
-                            static_cast<RETURN_TYPE>(std::get<kDefaultIndex>(cases)({})));
-                    } else {
-                        std::get<kDefaultIndex>(cases)({});
-                    }
-                }
-            } else {
-                if (!detail::NonDefaultCases<T>(object, type, result, std::move(cases))) {
-                    // Nothing matched. No default case.
-                    if constexpr (kHasReturnType) {
-                        new (result) RETURN_TYPE();
-                    }
-                }
-            }
-        } else {
-            // Object is nullptr, so no cases can match
-            if constexpr (kHasDefaultCase) {
-                // Evaluate default case.
-                if constexpr (kHasReturnType) {
-                    new (result)
-                        RETURN_TYPE(static_cast<RETURN_TYPE>(std::get<kDefaultIndex>(cases)({})));
-                } else {
-                    std::get<kDefaultIndex>(cases)({});
-                }
-            } else {
-                // No default case, no case can match.
-                if constexpr (kHasReturnType) {
-                    new (result) RETURN_TYPE();
-                }
-            }
-        }
-    }
-}
-
-/// Resolves to T if T is not nullptr_t, otherwise resolves to Ignore.
-template <typename T>
-using NullptrToIgnore = std::conditional_t<std::is_same_v<T, std::nullptr_t>, Ignore, T>;
-
-/// Resolves to `const TYPE` if any of `CASE_RETURN_TYPES` are const or pointer-to-const, otherwise
-/// resolves to TYPE.
-template <typename TYPE, typename... CASE_RETURN_TYPES>
-using PropagateReturnConst = std::conditional_t<
-    // Are any of the pointer-stripped types const?
-    (std::is_const_v<std::remove_pointer_t<CASE_RETURN_TYPES>> || ...),
-    const TYPE,  // Yes: Apply const to TYPE
-    TYPE>;       // No:  Passthrough
-
-/// SwitchReturnTypeImpl is the implementation of SwitchReturnType
-template <bool IS_CASTABLE, typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
-struct SwitchReturnTypeImpl;
-
-/// SwitchReturnTypeImpl specialization for non-castable case types and an explicitly specified
-/// return type.
-template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
-struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, REQUESTED_TYPE, CASE_RETURN_TYPES...> {
-    /// Resolves to `REQUESTED_TYPE`
-    using type = REQUESTED_TYPE;
-};
-
-/// SwitchReturnTypeImpl specialization for non-castable case types and an inferred return type.
-template <typename... CASE_RETURN_TYPES>
-struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, Infer, CASE_RETURN_TYPES...> {
-    /// Resolves to the common type for all the cases return types.
-    using type = std::common_type_t<CASE_RETURN_TYPES...>;
-};
-
-/// SwitchReturnTypeImpl specialization for castable case types and an explicitly specified return
-/// type.
-template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
-struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, REQUESTED_TYPE, CASE_RETURN_TYPES...> {
-  public:
-    /// Resolves to `const REQUESTED_TYPE*` or `REQUESTED_TYPE*`
-    using type = PropagateReturnConst<std::remove_pointer_t<REQUESTED_TYPE>, CASE_RETURN_TYPES...>*;
-};
-
-/// SwitchReturnTypeImpl specialization for castable case types and an inferred return type.
-template <typename... CASE_RETURN_TYPES>
-struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, Infer, CASE_RETURN_TYPES...> {
-  private:
-    using InferredType =
-        CastableCommonBase<detail::NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>;
-
-  public:
-    /// `const T*` or `T*`, where T is the common base type for all the castable case types.
-    using type = PropagateReturnConst<InferredType, CASE_RETURN_TYPES...>*;
-};
-
-/// Resolves to the return type for a Switch() with the requested return type `REQUESTED_TYPE` and
-/// case statement return types. If `REQUESTED_TYPE` is Infer then the return type will be inferred
-/// from the case return types.
-template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
-using SwitchReturnType = typename SwitchReturnTypeImpl<
-    IsCastable<NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>,
-    REQUESTED_TYPE,
-    CASE_RETURN_TYPES...>::type;
-
-}  // namespace detail
-
-/// Switch is used to dispatch one of the provided callback case handler functions based on the type
-/// of `object` and the parameter type of the case handlers. Switch will sequentially check the type
-/// of `object` against each of the switch case handler functions, and will invoke the first case
-/// handler function which has a parameter type that matches the object type. When a case handler is
-/// matched, it will be called with the single argument of `object` cast to the case handler's
-/// parameter type. Switch will invoke at most one case handler. Each of the case functions must
-/// have the signature `R(T*)` or `R(const T*)`, where `T` is the type matched by that case and `R`
-/// is the return type, consistent across all case handlers.
-///
-/// An optional default case function with the signature `R(Default)` can be used as the last case.
-/// This default case will be called if all previous cases failed to match.
-///
-/// If `object` is nullptr and a default case is provided, then the default case will be called. If
-/// `object` is nullptr and no default case is provided, then no cases will be called.
-///
-/// Example:
-/// ```
-/// Switch(object,
-///     [&](TypeA*) { /* ... */ },
-///     [&](TypeB*) { /* ... */ });
-///
-/// Switch(object,
-///     [&](TypeA*) { /* ... */ },
-///     [&](TypeB*) { /* ... */ },
-///     [&](Default) { /* Called if object is not TypeA or TypeB */ });
-/// ```
-///
-/// @param object the object who's type is used to
-/// @param cases the switch cases
-/// @return the value returned by the called case. If no cases matched, then the zero value for the
-/// consistent case type.
-template <typename RETURN_TYPE = detail::Infer, typename T = CastableBase, typename... CASES>
-inline auto Switch(T* object, CASES&&... cases) {
-    using ReturnType = detail::SwitchReturnType<RETURN_TYPE, traits::ReturnType<CASES>...>;
-    static constexpr bool kHasReturnType = !std::is_same_v<ReturnType, void>;
-
-    if constexpr (kHasReturnType) {
-        // Replacement for std::aligned_storage as this is broken on earlier versions of MSVC.
-        struct alignas(alignof(ReturnType)) ReturnStorage {
-            uint8_t data[sizeof(ReturnType)];
-        };
-        ReturnStorage storage;
-        auto* res = utils::Bitcast<ReturnType*>(&storage);
-        TINT_DEFER(res->~ReturnType());
-        detail::SwitchCases(object, res, std::forward_as_tuple(std::forward<CASES>(cases)...));
-        return *res;
-    } else {
-        detail::SwitchCases<T, void>(object, nullptr,
-                                     std::forward_as_tuple(std::forward<CASES>(cases)...));
-    }
-}
-
 }  // namespace tint
 
 TINT_CASTABLE_POP_DISABLE_WARNINGS();
diff --git a/src/tint/castable_test.cc b/src/tint/castable_test.cc
index c452c9b..57bd904 100644
--- a/src/tint/castable_test.cc
+++ b/src/tint/castable_test.cc
@@ -20,6 +20,7 @@
 #include "gtest/gtest.h"
 
 namespace tint {
+namespace {
 
 struct Animal : public tint::Castable<Animal> {};
 struct Amphibian : public tint::Castable<Amphibian, Animal> {};
@@ -31,8 +32,6 @@
 struct Gecko : public tint::Castable<Gecko, Lizard> {};
 struct Iguana : public tint::Castable<Iguana, Lizard> {};
 
-namespace {
-
 TEST(CastableBase, Is) {
     std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
     std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
@@ -230,512 +229,6 @@
     ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
 }
 
-TEST(Castable, SwitchNoDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        bool frog_matched_amphibian = false;
-        Switch(
-            frog.get(),  //
-            [&](Reptile*) { FAIL() << "frog is not reptile"; },
-            [&](Mammal*) { FAIL() << "frog is not mammal"; },
-            [&](Amphibian* amphibian) {
-                EXPECT_EQ(amphibian, frog.get());
-                frog_matched_amphibian = true;
-            });
-        EXPECT_TRUE(frog_matched_amphibian);
-    }
-    {
-        bool bear_matched_mammal = false;
-        Switch(
-            bear.get(),  //
-            [&](Reptile*) { FAIL() << "bear is not reptile"; },
-            [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
-            [&](Mammal* mammal) {
-                EXPECT_EQ(mammal, bear.get());
-                bear_matched_mammal = true;
-            });
-        EXPECT_TRUE(bear_matched_mammal);
-    }
-    {
-        bool gecko_matched_reptile = false;
-        Switch(
-            gecko.get(),  //
-            [&](Mammal*) { FAIL() << "gecko is not mammal"; },
-            [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
-            [&](Reptile* reptile) {
-                EXPECT_EQ(reptile, gecko.get());
-                gecko_matched_reptile = true;
-            });
-        EXPECT_TRUE(gecko_matched_reptile);
-    }
-}
-
-TEST(Castable, SwitchWithUnusedDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        bool frog_matched_amphibian = false;
-        Switch(
-            frog.get(),  //
-            [&](Reptile*) { FAIL() << "frog is not reptile"; },
-            [&](Mammal*) { FAIL() << "frog is not mammal"; },
-            [&](Amphibian* amphibian) {
-                EXPECT_EQ(amphibian, frog.get());
-                frog_matched_amphibian = true;
-            },
-            [&](Default) { FAIL() << "default should not have been selected"; });
-        EXPECT_TRUE(frog_matched_amphibian);
-    }
-    {
-        bool bear_matched_mammal = false;
-        Switch(
-            bear.get(),  //
-            [&](Reptile*) { FAIL() << "bear is not reptile"; },
-            [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
-            [&](Mammal* mammal) {
-                EXPECT_EQ(mammal, bear.get());
-                bear_matched_mammal = true;
-            },
-            [&](Default) { FAIL() << "default should not have been selected"; });
-        EXPECT_TRUE(bear_matched_mammal);
-    }
-    {
-        bool gecko_matched_reptile = false;
-        Switch(
-            gecko.get(),  //
-            [&](Mammal*) { FAIL() << "gecko is not mammal"; },
-            [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
-            [&](Reptile* reptile) {
-                EXPECT_EQ(reptile, gecko.get());
-                gecko_matched_reptile = true;
-            },
-            [&](Default) { FAIL() << "default should not have been selected"; });
-        EXPECT_TRUE(gecko_matched_reptile);
-    }
-}
-
-TEST(Castable, SwitchDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        bool frog_matched_default = false;
-        Switch(
-            frog.get(),  //
-            [&](Reptile*) { FAIL() << "frog is not reptile"; },
-            [&](Mammal*) { FAIL() << "frog is not mammal"; },
-            [&](Default) { frog_matched_default = true; });
-        EXPECT_TRUE(frog_matched_default);
-    }
-    {
-        bool bear_matched_default = false;
-        Switch(
-            bear.get(),  //
-            [&](Reptile*) { FAIL() << "bear is not reptile"; },
-            [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
-            [&](Default) { bear_matched_default = true; });
-        EXPECT_TRUE(bear_matched_default);
-    }
-    {
-        bool gecko_matched_default = false;
-        Switch(
-            gecko.get(),  //
-            [&](Mammal*) { FAIL() << "gecko is not mammal"; },
-            [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
-            [&](Default) { gecko_matched_default = true; });
-        EXPECT_TRUE(gecko_matched_default);
-    }
-}
-
-TEST(Castable, SwitchMatchFirst) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    {
-        bool frog_matched_animal = false;
-        Switch(
-            frog.get(),
-            [&](Animal* animal) {
-                EXPECT_EQ(animal, frog.get());
-                frog_matched_animal = true;
-            },
-            [&](Amphibian*) { FAIL() << "animal should have been matched first"; });
-        EXPECT_TRUE(frog_matched_animal);
-    }
-    {
-        bool frog_matched_amphibian = false;
-        Switch(
-            frog.get(),
-            [&](Amphibian* amphibain) {
-                EXPECT_EQ(amphibain, frog.get());
-                frog_matched_amphibian = true;
-            },
-            [&](Animal*) { FAIL() << "amphibian should have been matched first"; });
-        EXPECT_TRUE(frog_matched_amphibian);
-    }
-}
-
-TEST(Castable, SwitchReturnValueWithDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        const char* result = Switch(
-            frog.get(),                              //
-            [](Mammal*) { return "mammal"; },        //
-            [](Amphibian*) { return "amphibian"; },  //
-            [](Default) { return "unknown"; });
-        static_assert(std::is_same_v<decltype(result), const char*>);
-        EXPECT_EQ(std::string(result), "amphibian");
-    }
-    {
-        const char* result = Switch(
-            bear.get(),                              //
-            [](Mammal*) { return "mammal"; },        //
-            [](Amphibian*) { return "amphibian"; },  //
-            [](Default) { return "unknown"; });
-        static_assert(std::is_same_v<decltype(result), const char*>);
-        EXPECT_EQ(std::string(result), "mammal");
-    }
-    {
-        const char* result = Switch(
-            gecko.get(),                             //
-            [](Mammal*) { return "mammal"; },        //
-            [](Amphibian*) { return "amphibian"; },  //
-            [](Default) { return "unknown"; });
-        static_assert(std::is_same_v<decltype(result), const char*>);
-        EXPECT_EQ(std::string(result), "unknown");
-    }
-}
-
-TEST(Castable, SwitchReturnValueWithoutDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        const char* result = Switch(
-            frog.get(),                        //
-            [](Mammal*) { return "mammal"; },  //
-            [](Amphibian*) { return "amphibian"; });
-        static_assert(std::is_same_v<decltype(result), const char*>);
-        EXPECT_EQ(std::string(result), "amphibian");
-    }
-    {
-        const char* result = Switch(
-            bear.get(),                        //
-            [](Mammal*) { return "mammal"; },  //
-            [](Amphibian*) { return "amphibian"; });
-        static_assert(std::is_same_v<decltype(result), const char*>);
-        EXPECT_EQ(std::string(result), "mammal");
-    }
-    {
-        auto* result = Switch(
-            gecko.get(),                       //
-            [](Mammal*) { return "mammal"; },  //
-            [](Amphibian*) { return "amphibian"; });
-        static_assert(std::is_same_v<decltype(result), const char*>);
-        EXPECT_EQ(result, nullptr);
-    }
-}
-
-TEST(Castable, SwitchInferPODReturnTypeWithDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        auto result = Switch(
-            frog.get(),                       //
-            [](Mammal*) { return 1; },        //
-            [](Amphibian*) { return 2.0f; },  //
-            [](Default) { return 3.0; });
-        static_assert(std::is_same_v<decltype(result), double>);
-        EXPECT_EQ(result, 2.0);
-    }
-    {
-        auto result = Switch(
-            bear.get(),                       //
-            [](Mammal*) { return 1.0; },      //
-            [](Amphibian*) { return 2.0f; },  //
-            [](Default) { return 3; });
-        static_assert(std::is_same_v<decltype(result), double>);
-        EXPECT_EQ(result, 1.0);
-    }
-    {
-        auto result = Switch(
-            gecko.get(),                   //
-            [](Mammal*) { return 1.0f; },  //
-            [](Amphibian*) { return 2; },  //
-            [](Default) { return 3.0; });
-        static_assert(std::is_same_v<decltype(result), double>);
-        EXPECT_EQ(result, 3.0);
-    }
-}
-
-TEST(Castable, SwitchInferPODReturnTypeWithoutDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        auto result = Switch(
-            frog.get(),                 //
-            [](Mammal*) { return 1; },  //
-            [](Amphibian*) { return 2.0f; });
-        static_assert(std::is_same_v<decltype(result), float>);
-        EXPECT_EQ(result, 2.0f);
-    }
-    {
-        auto result = Switch(
-            bear.get(),                    //
-            [](Mammal*) { return 1.0f; },  //
-            [](Amphibian*) { return 2; });
-        static_assert(std::is_same_v<decltype(result), float>);
-        EXPECT_EQ(result, 1.0f);
-    }
-    {
-        auto result = Switch(
-            gecko.get(),                  //
-            [](Mammal*) { return 1.0; },  //
-            [](Amphibian*) { return 2.0f; });
-        static_assert(std::is_same_v<decltype(result), double>);
-        EXPECT_EQ(result, 0.0);
-    }
-}
-
-TEST(Castable, SwitchInferCastableReturnTypeWithDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        auto* result = Switch(
-            frog.get(),                          //
-            [](Mammal* p) { return p; },         //
-            [](Amphibian*) { return nullptr; },  //
-            [](Default) { return nullptr; });
-        static_assert(std::is_same_v<decltype(result), Mammal*>);
-        EXPECT_EQ(result, nullptr);
-    }
-    {
-        auto* result = Switch(
-            bear.get(),                   //
-            [](Mammal* p) { return p; },  //
-            [](Amphibian* p) { return const_cast<const Amphibian*>(p); },
-            [](Default) { return nullptr; });
-        static_assert(std::is_same_v<decltype(result), const Animal*>);
-        EXPECT_EQ(result, bear.get());
-    }
-    {
-        auto* result = Switch(
-            gecko.get(),                     //
-            [](Mammal* p) { return p; },     //
-            [](Amphibian* p) { return p; },  //
-            [](Default) -> CastableBase* { return nullptr; });
-        static_assert(std::is_same_v<decltype(result), CastableBase*>);
-        EXPECT_EQ(result, nullptr);
-    }
-}
-
-TEST(Castable, SwitchInferCastableReturnTypeWithoutDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        auto* result = Switch(
-            frog.get(),                   //
-            [](Mammal* p) { return p; },  //
-            [](Amphibian*) { return nullptr; });
-        static_assert(std::is_same_v<decltype(result), Mammal*>);
-        EXPECT_EQ(result, nullptr);
-    }
-    {
-        auto* result = Switch(
-            bear.get(),                                                     //
-            [](Mammal* p) { return p; },                                    //
-            [](Amphibian* p) { return const_cast<const Amphibian*>(p); });  //
-        static_assert(std::is_same_v<decltype(result), const Animal*>);
-        EXPECT_EQ(result, bear.get());
-    }
-    {
-        auto* result = Switch(
-            gecko.get(),                  //
-            [](Mammal* p) { return p; },  //
-            [](Amphibian* p) { return p; });
-        static_assert(std::is_same_v<decltype(result), Animal*>);
-        EXPECT_EQ(result, nullptr);
-    }
-}
-
-TEST(Castable, SwitchExplicitPODReturnTypeWithDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        auto result = Switch<double>(
-            frog.get(),                       //
-            [](Mammal*) { return 1; },        //
-            [](Amphibian*) { return 2.0f; },  //
-            [](Default) { return 3.0; });
-        static_assert(std::is_same_v<decltype(result), double>);
-        EXPECT_EQ(result, 2.0f);
-    }
-    {
-        auto result = Switch<double>(
-            bear.get(),                    //
-            [](Mammal*) { return 1; },     //
-            [](Amphibian*) { return 2; },  //
-            [](Default) { return 3; });
-        static_assert(std::is_same_v<decltype(result), double>);
-        EXPECT_EQ(result, 1.0f);
-    }
-    {
-        auto result = Switch<double>(
-            gecko.get(),                      //
-            [](Mammal*) { return 1.0f; },     //
-            [](Amphibian*) { return 2.0f; },  //
-            [](Default) { return 3.0f; });
-        static_assert(std::is_same_v<decltype(result), double>);
-        EXPECT_EQ(result, 3.0f);
-    }
-}
-
-TEST(Castable, SwitchExplicitPODReturnTypeWithoutDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        auto result = Switch<double>(
-            frog.get(),                 //
-            [](Mammal*) { return 1; },  //
-            [](Amphibian*) { return 2.0f; });
-        static_assert(std::is_same_v<decltype(result), double>);
-        EXPECT_EQ(result, 2.0f);
-    }
-    {
-        auto result = Switch<double>(
-            bear.get(),                    //
-            [](Mammal*) { return 1.0f; },  //
-            [](Amphibian*) { return 2; });
-        static_assert(std::is_same_v<decltype(result), double>);
-        EXPECT_EQ(result, 1.0f);
-    }
-    {
-        auto result = Switch<double>(
-            gecko.get(),                  //
-            [](Mammal*) { return 1.0; },  //
-            [](Amphibian*) { return 2.0f; });
-        static_assert(std::is_same_v<decltype(result), double>);
-        EXPECT_EQ(result, 0.0);
-    }
-}
-
-TEST(Castable, SwitchExplicitCastableReturnTypeWithDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        auto* result = Switch<Animal>(
-            frog.get(),                          //
-            [](Mammal* p) { return p; },         //
-            [](Amphibian*) { return nullptr; },  //
-            [](Default) { return nullptr; });
-        static_assert(std::is_same_v<decltype(result), Animal*>);
-        EXPECT_EQ(result, nullptr);
-    }
-    {
-        auto* result = Switch<CastableBase>(
-            bear.get(),                   //
-            [](Mammal* p) { return p; },  //
-            [](Amphibian* p) { return const_cast<const Amphibian*>(p); },
-            [](Default) { return nullptr; });
-        static_assert(std::is_same_v<decltype(result), const CastableBase*>);
-        EXPECT_EQ(result, bear.get());
-    }
-    {
-        auto* result = Switch<const Animal>(
-            gecko.get(),                     //
-            [](Mammal* p) { return p; },     //
-            [](Amphibian* p) { return p; },  //
-            [](Default) { return nullptr; });
-        static_assert(std::is_same_v<decltype(result), const Animal*>);
-        EXPECT_EQ(result, nullptr);
-    }
-}
-
-TEST(Castable, SwitchExplicitCastableReturnTypeWithoutDefault) {
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
-    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
-    {
-        auto* result = Switch<Animal>(
-            frog.get(),                   //
-            [](Mammal* p) { return p; },  //
-            [](Amphibian*) { return nullptr; });
-        static_assert(std::is_same_v<decltype(result), Animal*>);
-        EXPECT_EQ(result, nullptr);
-    }
-    {
-        auto* result = Switch<CastableBase>(
-            bear.get(),                                                     //
-            [](Mammal* p) { return p; },                                    //
-            [](Amphibian* p) { return const_cast<const Amphibian*>(p); });  //
-        static_assert(std::is_same_v<decltype(result), const CastableBase*>);
-        EXPECT_EQ(result, bear.get());
-    }
-    {
-        auto* result = Switch<const Animal*>(
-            gecko.get(),                  //
-            [](Mammal* p) { return p; },  //
-            [](Amphibian* p) { return p; });
-        static_assert(std::is_same_v<decltype(result), const Animal*>);
-        EXPECT_EQ(result, nullptr);
-    }
-}
-
-TEST(Castable, SwitchNull) {
-    Animal* null = nullptr;
-    Switch(
-        null,  //
-        [&](Amphibian*) { FAIL() << "should not be called"; },
-        [&](Animal*) { FAIL() << "should not be called"; });
-}
-
-TEST(Castable, SwitchNullNoDefault) {
-    Animal* null = nullptr;
-    bool default_called = false;
-    Switch(
-        null,  //
-        [&](Amphibian*) { FAIL() << "should not be called"; },
-        [&](Animal*) { FAIL() << "should not be called"; },
-        [&](Default) { default_called = true; });
-    EXPECT_TRUE(default_called);
-}
-
-TEST(Castable, SwitchReturnNoDefaultInitializer) {
-    struct Object {
-        explicit Object(int v) : value(v) {}
-        int value;
-    };
-
-    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
-    {
-        auto result = Switch(
-            frog.get(),                            //
-            [](Mammal*) { return Object(1); },     //
-            [](Amphibian*) { return Object(2); },  //
-            [](Default) { return Object(3); });
-        static_assert(std::is_same_v<decltype(result), Object>);
-        EXPECT_EQ(result.value, 2);
-    }
-    {
-        auto result = Switch(
-            frog.get(),                         //
-            [](Mammal*) { return Object(1); },  //
-            [](Default) { return Object(3); });
-        static_assert(std::is_same_v<decltype(result), Object>);
-        EXPECT_EQ(result.value, 3);
-    }
-}
-
 // IsCastable static tests
 static_assert(IsCastable<CastableBase>);
 static_assert(IsCastable<Animal>);
diff --git a/src/tint/cmd/BUILD.gn b/src/tint/cmd/BUILD.gn
index b4ff6d5..946bd36 100644
--- a/src/tint/cmd/BUILD.gn
+++ b/src/tint/cmd/BUILD.gn
@@ -17,6 +17,8 @@
 
 source_set("tint_cmd_helper") {
   sources = [
+    "generate_external_texture_bindings.cc",
+    "generate_external_texture_bindings.h",
     "helper.cc",
     "helper.h",
   ]
diff --git a/src/tint/cmd/CMakeLists.txt b/src/tint/cmd/CMakeLists.txt
index 6da9701..142595b 100644
--- a/src/tint/cmd/CMakeLists.txt
+++ b/src/tint/cmd/CMakeLists.txt
@@ -15,6 +15,8 @@
 ## Tint executable
 add_executable(tint "")
 target_sources(tint PRIVATE
+  "generate_external_texture_bindings.cc"
+  "generate_external_texture_bindings.h"
   "helper.cc"
   "helper.h"
   "main.cc"
diff --git a/src/tint/writer/generate_external_texture_bindings.cc b/src/tint/cmd/generate_external_texture_bindings.cc
similarity index 82%
rename from src/tint/writer/generate_external_texture_bindings.cc
rename to src/tint/cmd/generate_external_texture_bindings.cc
index 6e5daf0..1b6ca74 100644
--- a/src/tint/writer/generate_external_texture_bindings.cc
+++ b/src/tint/cmd/generate_external_texture_bindings.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/writer/generate_external_texture_bindings.h"
+#include "src/tint/cmd/generate_external_texture_bindings.h"
 
 #include <algorithm>
 #include <unordered_map>
@@ -20,12 +20,13 @@
 
 #include "src/tint/ast/module.h"
 #include "src/tint/program.h"
+#include "src/tint/sem/binding_point.h"
 #include "src/tint/sem/variable.h"
 #include "src/tint/type/external_texture.h"
 
-namespace tint::writer {
+namespace tint::cmd {
 
-transform::MultiplanarExternalTexture::BindingsMap GenerateExternalTextureBindings(
+writer::ExternalTextureOptions::BindingsMap GenerateExternalTextureBindings(
     const Program* program) {
     // TODO(tint:1491): Use Inspector once we can get binding info for all
     // variables, not just those referenced by entry points.
@@ -45,15 +46,17 @@
         }
     }
 
-    transform::MultiplanarExternalTexture::BindingsMap new_bindings_map;
+    writer::ExternalTextureOptions::BindingsMap new_bindings_map;
     for (auto bp : ext_tex_bps) {
         uint32_t g = bp.group;
         uint32_t& next_num = group_to_next_binding_number[g];
         auto new_bps =
-            transform::MultiplanarExternalTexture::BindingPoints{{g, next_num++}, {g, next_num++}};
+            writer::ExternalTextureOptions::BindingPoints{{g, next_num++}, {g, next_num++}};
+
         new_bindings_map[bp] = new_bps;
     }
+
     return new_bindings_map;
 }
 
-}  // namespace tint::writer
+}  // namespace tint::cmd
diff --git a/src/tint/cmd/generate_external_texture_bindings.h b/src/tint/cmd/generate_external_texture_bindings.h
new file mode 100644
index 0000000..c6a842d
--- /dev/null
+++ b/src/tint/cmd/generate_external_texture_bindings.h
@@ -0,0 +1,26 @@
+// Copyright 2022 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_TINT_CMD_GENERATE_EXTERNAL_TEXTURE_BINDINGS_H_
+#define SRC_TINT_CMD_GENERATE_EXTERNAL_TEXTURE_BINDINGS_H_
+
+#include "tint/tint.h"
+
+namespace tint::cmd {
+
+writer::ExternalTextureOptions::BindingsMap GenerateExternalTextureBindings(const Program* program);
+
+}  // namespace tint::cmd
+
+#endif  // SRC_TINT_CMD_GENERATE_EXTERNAL_TEXTURE_BINDINGS_H_
diff --git a/src/tint/writer/generate_external_texture_bindings_test.cc b/src/tint/cmd/generate_external_texture_bindings_test.cc
similarity index 88%
rename from src/tint/writer/generate_external_texture_bindings_test.cc
rename to src/tint/cmd/generate_external_texture_bindings_test.cc
index d6b22ac..2c19675 100644
--- a/src/tint/writer/generate_external_texture_bindings_test.cc
+++ b/src/tint/cmd/generate_external_texture_bindings_test.cc
@@ -15,10 +15,11 @@
 #include <utility>
 
 #include "gtest/gtest.h"
+#include "src/tint/cmd/generate_external_texture_bindings.h"
 #include "src/tint/program_builder.h"
-#include "src/tint/writer/generate_external_texture_bindings.h"
+#include "src/tint/writer/binding_point.h"
 
-namespace tint::writer {
+namespace tint::cmd {
 namespace {
 
 using namespace tint::number_suffixes;  // NOLINT
@@ -45,7 +46,7 @@
     auto bindings = GenerateExternalTextureBindings(&program);
     ASSERT_EQ(bindings.size(), 1u);
 
-    auto to = bindings[transform::BindingPoint{0, 0}];
+    auto to = bindings[writer::BindingPoint{0, 0}];
     ASSERT_EQ(to.plane_1.group, 0u);
     ASSERT_EQ(to.params.group, 0u);
     ASSERT_EQ(to.plane_1.binding, 1u);
@@ -62,13 +63,13 @@
     auto bindings = GenerateExternalTextureBindings(&program);
     ASSERT_EQ(bindings.size(), 2u);
 
-    auto to0 = bindings[transform::BindingPoint{0, 0}];
+    auto to0 = bindings[writer::BindingPoint{0, 0}];
     ASSERT_EQ(to0.plane_1.group, 0u);
     ASSERT_EQ(to0.params.group, 0u);
     ASSERT_EQ(to0.plane_1.binding, 2u);
     ASSERT_EQ(to0.params.binding, 3u);
 
-    auto to1 = bindings[transform::BindingPoint{0, 1}];
+    auto to1 = bindings[writer::BindingPoint{0, 1}];
     ASSERT_EQ(to1.plane_1.group, 0u);
     ASSERT_EQ(to1.params.group, 0u);
     ASSERT_EQ(to1.plane_1.binding, 4u);
@@ -85,13 +86,13 @@
     auto bindings = GenerateExternalTextureBindings(&program);
     ASSERT_EQ(bindings.size(), 2u);
 
-    auto to0 = bindings[transform::BindingPoint{0, 0}];
+    auto to0 = bindings[writer::BindingPoint{0, 0}];
     ASSERT_EQ(to0.plane_1.group, 0u);
     ASSERT_EQ(to0.params.group, 0u);
     ASSERT_EQ(to0.plane_1.binding, 1u);
     ASSERT_EQ(to0.params.binding, 2u);
 
-    auto to1 = bindings[transform::BindingPoint{1, 0}];
+    auto to1 = bindings[writer::BindingPoint{1, 0}];
     ASSERT_EQ(to1.plane_1.group, 1u);
     ASSERT_EQ(to1.params.group, 1u);
     ASSERT_EQ(to1.plane_1.binding, 1u);
@@ -111,13 +112,13 @@
     auto bindings = GenerateExternalTextureBindings(&program);
     ASSERT_EQ(bindings.size(), 2u);
 
-    auto to0 = bindings[transform::BindingPoint{0, 1}];
+    auto to0 = bindings[writer::BindingPoint{0, 1}];
     ASSERT_EQ(to0.plane_1.group, 0u);
     ASSERT_EQ(to0.params.group, 0u);
     ASSERT_EQ(to0.plane_1.binding, 5u);
     ASSERT_EQ(to0.params.binding, 6u);
 
-    auto to1 = bindings[transform::BindingPoint{0, 3}];
+    auto to1 = bindings[writer::BindingPoint{0, 3}];
     ASSERT_EQ(to1.plane_1.group, 0u);
     ASSERT_EQ(to1.params.group, 0u);
     ASSERT_EQ(to1.plane_1.binding, 7u);
@@ -125,4 +126,4 @@
 }
 
 }  // namespace
-}  // namespace tint::writer
+}  // namespace tint::cmd
diff --git a/src/tint/cmd/helper.cc b/src/tint/cmd/helper.cc
index 53013fe..49b970a 100644
--- a/src/tint/cmd/helper.cc
+++ b/src/tint/cmd/helper.cc
@@ -18,7 +18,7 @@
 #include <utility>
 #include <vector>
 
-#if TINT_BUILD_SPV_WRITER
+#if TINT_BUILD_SPV_READER
 #include "spirv-tools/libspirv.hpp"
 #endif
 
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index 0b2aad2..898f0ac 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -29,11 +29,17 @@
 #include "glslang/Public/ShaderLang.h"
 #endif  // TINT_BUILD_GLSL_WRITER
 
+#if TINT_BUILD_SYNTAX_TREE_WRITER
+#include "src/tint/writer/syntax_tree/generator.h"  // nogncheck
+
+#endif  // TINT_BUILD_SYNTAX_TREE_WRITER
+
 #if TINT_BUILD_SPV_READER
 #include "spirv-tools/libspirv.hpp"
 #endif  // TINT_BUILD_SPV_READER
 
 #include "src/tint/ast/module.h"
+#include "src/tint/cmd/generate_external_texture_bindings.h"
 #include "src/tint/cmd/helper.h"
 #include "src/tint/utils/io/command.h"
 #include "src/tint/utils/string.h"
@@ -106,6 +112,10 @@
     bool dump_ir = false;
     bool dump_ir_graph = false;
 #endif  // TINT_BUILD_IR
+
+#if TINT_BUILD_SYNTAX_TREE_WRITER
+    bool dump_syntax_tree = false;
+#endif  // TINB_BUILD_SYNTAX_TREE_WRITER
 };
 
 const char kUsage[] = R"(Usage: tint [options] <input-file>
@@ -383,6 +393,10 @@
         } else if (arg == "--dump-ir-graph") {
             opts->dump_ir_graph = true;
 #endif  // TINT_BUILD_IR
+#if TINT_BUILD_SYNTAX_TREE_WRITER
+        } else if (arg == "--dump-ast") {
+            opts->dump_syntax_tree = true;
+#endif  // TINT_BUILD_SYNTAX_TREE_WRITER
         } else if (arg == "--xcrun") {
             ++i;
             if (i >= args.size()) {
@@ -536,7 +550,8 @@
     tint::writer::spirv::Options gen_options;
     gen_options.disable_robustness = !options.enable_robustness;
     gen_options.disable_workgroup_init = options.disable_workgroup_init;
-    gen_options.generate_external_texture_bindings = true;
+    gen_options.external_texture_options.bindings_map =
+        tint::cmd::GenerateExternalTextureBindings(program);
     auto result = tint::writer::spirv::Generate(program, gen_options);
     if (!result.success) {
         tint::cmd::PrintWGSL(std::cerr, *program);
@@ -643,7 +658,8 @@
     tint::writer::msl::Options gen_options;
     gen_options.disable_robustness = !options.enable_robustness;
     gen_options.disable_workgroup_init = options.disable_workgroup_init;
-    gen_options.generate_external_texture_bindings = true;
+    gen_options.external_texture_options.bindings_map =
+        tint::cmd::GenerateExternalTextureBindings(input_program);
     auto result = tint::writer::msl::Generate(input_program, gen_options);
     if (!result.success) {
         tint::cmd::PrintWGSL(std::cerr, *program);
@@ -704,7 +720,8 @@
     tint::writer::hlsl::Options gen_options;
     gen_options.disable_robustness = !options.enable_robustness;
     gen_options.disable_workgroup_init = options.disable_workgroup_init;
-    gen_options.generate_external_texture_bindings = true;
+    gen_options.external_texture_options.bindings_map =
+        tint::cmd::GenerateExternalTextureBindings(program);
     gen_options.root_constant_binding_point = options.hlsl_root_constant_binding_point;
     auto result = tint::writer::hlsl::Generate(program, gen_options);
     if (!result.success) {
@@ -738,8 +755,8 @@
 
                 auto enable_list = program->AST().Enables();
                 bool dxc_require_16bit_types = false;
-                for (auto enable : enable_list) {
-                    if (enable->extension == tint::builtin::Extension::kF16) {
+                for (auto* enable : enable_list) {
+                    if (enable->HasExtension(tint::builtin::Extension::kF16)) {
                         dxc_require_16bit_types = true;
                         break;
                     }
@@ -843,7 +860,8 @@
     auto generate = [&](const tint::Program* prg, const std::string entry_point_name) -> bool {
         tint::writer::glsl::Options gen_options;
         gen_options.disable_robustness = !options.enable_robustness;
-        gen_options.generate_external_texture_bindings = true;
+        gen_options.external_texture_options.bindings_map =
+            tint::cmd::GenerateExternalTextureBindings(prg);
         auto result = tint::writer::glsl::Generate(prg, gen_options, entry_point_name);
         if (!result.success) {
             tint::cmd::PrintWGSL(std::cerr, *prg);
@@ -990,55 +1008,6 @@
              m.Add<tint::transform::SubstituteOverride>();
              return true;
          }},
-        {"multiplaner_external_texture",
-         [](tint::inspector::Inspector& inspector, tint::transform::Manager& m,
-            tint::transform::DataMap& i) {
-             using MET = tint::transform::MultiplanarExternalTexture;
-
-             // Generate the MultiplanarExternalTexture::NewBindingPoints by finding two free
-             // binding points. We may wish to expose these binding points via a command line flag
-             // in the future.
-
-             // Set of all the group-0 bindings in use.
-             std::unordered_set<uint32_t> group0_bindings_in_use;
-             auto allocate_binding = [&] {
-                 for (uint32_t idx = 0;; idx++) {
-                     auto binding = tint::transform::BindingPoint{0u, idx};
-                     if (group0_bindings_in_use.emplace(idx).second) {
-                         return binding;
-                     }
-                 }
-             };
-             // Populate group0_bindings_in_use with the existing bindings across all entry points.
-             for (auto ep : inspector.GetEntryPoints()) {
-                 for (auto binding : inspector.GetResourceBindings(ep.name)) {
-                     if (binding.bind_group == 0) {
-                         group0_bindings_in_use.emplace(binding.binding);
-                     }
-                 }
-             }
-             // Allocate new binding points for the external texture's planes and parameters.
-             MET::BindingsMap met_bindings;
-             for (auto ep : inspector.GetEntryPoints()) {
-                 for (auto ext_tex : inspector.GetExternalTextureResourceBindings(ep.name)) {
-                     auto binding = tint::transform::BindingPoint{
-                         ext_tex.bind_group,
-                         ext_tex.binding,
-                     };
-                     if (met_bindings.count(binding)) {
-                         continue;
-                     }
-                     met_bindings.emplace(binding, MET::BindingPoints{
-                                                       /* plane_1 */ allocate_binding(),
-                                                       /* params */ allocate_binding(),
-                                                   });
-                 }
-             }
-
-             i.Add<MET::NewBindingPoints>(std::move(met_bindings));
-             m.Add<MET>();
-             return true;
-         }},
     };
     auto transform_names = [&] {
         tint::utils::StringStream names;
@@ -1055,6 +1024,9 @@
             "  --dump-ir                 -- Writes the IR to stdout\n"
             "  --dump-ir-graph           -- Writes the IR graph to 'tint.dot' as a dot graph\n";
 #endif  // TINT_BUILD_IR
+#if TINT_BUILD_SYNTAX_TREE_WRITER
+        usage += "  --dump-ast                -- Writes the AST to stdout\n";
+#endif  // TINT_BUILD_SYNTAX_TREE_WRITER
 
         std::cout << usage << std::endl;
         return 0;
@@ -1092,6 +1064,18 @@
         return 1;
     }
 
+#if TINT_BUILD_SYNTAX_TREE_WRITER
+    if (options.dump_syntax_tree) {
+        tint::writer::syntax_tree::Options gen_options;
+        auto result = tint::writer::syntax_tree::Generate(program.get(), gen_options);
+        if (!result.success) {
+            std::cerr << "Failed to dump AST: " << result.error << std::endl;
+        } else {
+            std::cout << result.ast << std::endl;
+        }
+    }
+#endif  // TINT_BUILD_SYNTAX_TREE_WRITER
+
 #if TINT_BUILD_IR
     if (options.dump_ir || options.dump_ir_graph) {
         auto result = tint::ir::Module::FromProgram(program.get());
diff --git a/src/tint/constant/composite.h b/src/tint/constant/composite.h
index 3bd4973..ef7233c 100644
--- a/src/tint/constant/composite.h
+++ b/src/tint/constant/composite.h
@@ -25,31 +25,37 @@
 namespace tint::constant {
 
 /// Composite holds a number of mixed child values.
-/// Composite may be of a vector, matrix or array type.
+/// Composite may be of a vector, matrix, array or structure type.
 /// If each element is the same type and value, then a Splat would be a more efficient constant
 /// implementation. Use CreateComposite() to create the appropriate type.
-class Composite : public Castable<Composite, constant::Value> {
+class Composite : public Castable<Composite, Value> {
   public:
     /// Constructor
     /// @param t the compsite type
     /// @param els the composite elements
     /// @param all_0 true if all elements are 0
     /// @param any_0 true if any element is 0
-    Composite(const type::Type* t,
-              utils::VectorRef<const constant::Value*> els,
-              bool all_0,
-              bool any_0);
+    Composite(const type::Type* t, utils::VectorRef<const Value*> els, bool all_0, bool any_0);
     ~Composite() override;
 
+    /// @copydoc Value::Type()
     const type::Type* Type() const override { return type; }
 
-    const constant::Value* Index(size_t i) const override {
+    /// @copydoc Value::Index()
+    const Value* Index(size_t i) const override {
         return i < elements.Length() ? elements[i] : nullptr;
     }
 
+    /// @copydoc Value::NumElements()
+    size_t NumElements() const override { return elements.Length(); }
+
+    /// @copydoc Value::AllZero()
     bool AllZero() const override { return all_zero; }
+
+    /// @copydoc Value::AnyZero()
     bool AnyZero() const override { return any_zero; }
-    bool AllEqual() const override { return false; }
+
+    /// @copydoc Value::Hash()
     size_t Hash() const override { return hash; }
 
     /// Clones the constant into the provided context
@@ -60,7 +66,7 @@
     /// The composite type
     type::Type const* const type;
     /// The composite elements
-    const utils::Vector<const constant::Value*, 4> elements;
+    const utils::Vector<const Value*, 4> elements;
     /// True if all elements are zero
     const bool all_zero;
     /// True if any element is zero
@@ -69,6 +75,7 @@
     const size_t hash;
 
   protected:
+    /// @copydoc Value::InternalValue()
     std::variant<std::monostate, AInt, AFloat> InternalValue() const override { return {}; }
 
   private:
diff --git a/src/tint/constant/composite_test.cc b/src/tint/constant/composite_test.cc
index 6fc532d..fd083db 100644
--- a/src/tint/constant/composite_test.cc
+++ b/src/tint/constant/composite_test.cc
@@ -56,20 +56,6 @@
     EXPECT_FALSE(compositeNone->AnyZero());
 }
 
-TEST_F(ConstantTest_Composite, AllEqual) {
-    auto* f32 = create<type::F32>();
-
-    auto* fPos0 = create<Scalar<tint::f32>>(f32, 0_f);
-    auto* fNeg0 = create<Scalar<tint::f32>>(f32, -0_f);
-    auto* fPos1 = create<Scalar<tint::f32>>(f32, 1_f);
-
-    auto* compositeEq = create<Composite>(f32, utils::Vector{fPos0, fPos0});
-    auto* compositeNe = create<Composite>(f32, utils::Vector{fNeg0, fPos1, fPos0});
-
-    EXPECT_TRUE(compositeEq->AllEqual());
-    EXPECT_FALSE(compositeNe->AllZero());
-}
-
 TEST_F(ConstantTest_Composite, Index) {
     auto* f32 = create<type::F32>();
 
diff --git a/src/tint/constant/scalar.h b/src/tint/constant/scalar.h
index a412d91..fc6aef0 100644
--- a/src/tint/constant/scalar.h
+++ b/src/tint/constant/scalar.h
@@ -25,7 +25,7 @@
 
 /// Scalar holds a single scalar or abstract-numeric value.
 template <typename T>
-class Scalar : public Castable<Scalar<T>, constant::Value> {
+class Scalar : public Castable<Scalar<T>, Value> {
   public:
     static_assert(!std::is_same_v<UnwrapNumber<T>, T> || std::is_same_v<T, bool>,
                   "T must be a Number or bool");
@@ -40,13 +40,22 @@
     }
     ~Scalar() override = default;
 
+    /// @copydoc Value::Type()
     const type::Type* Type() const override { return type; }
 
-    const constant::Value* Index(size_t) const override { return nullptr; }
+    /// @return nullptr, as Scalar does not hold any elements.
+    const Value* Index(size_t) const override { return nullptr; }
 
+    /// @copydoc Value::NumElements()
+    size_t NumElements() const override { return 1; }
+
+    /// @copydoc Value::AllZero()
     bool AllZero() const override { return IsPositiveZero(); }
+
+    /// @copydoc Value::AnyZero()
     bool AnyZero() const override { return IsPositiveZero(); }
-    bool AllEqual() const override { return true; }
+
+    /// @copydoc Value::Hash()
     size_t Hash() const override { return utils::Hash(type, ValueOf()); }
 
     /// Clones the constant into the provided context
@@ -79,6 +88,7 @@
     const T value;
 
   protected:
+    /// @copydoc Value::InternalValue()
     std::variant<std::monostate, AInt, AFloat> InternalValue() const override {
         if constexpr (IsFloatingPoint<UnwrapNumber<T>>) {
             return static_cast<AFloat>(value);
diff --git a/src/tint/constant/scalar_test.cc b/src/tint/constant/scalar_test.cc
index e05cca0..6dedf75 100644
--- a/src/tint/constant/scalar_test.cc
+++ b/src/tint/constant/scalar_test.cc
@@ -155,72 +155,6 @@
     EXPECT_FALSE(aiNeg1->AnyZero());
 }
 
-TEST_F(ConstantTest_Scalar, AllEqual) {
-    auto* i32 = create<type::I32>();
-    auto* u32 = create<type::U32>();
-    auto* f16 = create<type::F16>();
-    auto* f32 = create<type::F32>();
-    auto* bool_ = create<type::Bool>();
-
-    auto* i0 = create<Scalar<tint::i32>>(i32, 0_i);
-    auto* iPos1 = create<Scalar<tint::i32>>(i32, 1_i);
-    auto* iNeg1 = create<Scalar<tint::i32>>(i32, -1_i);
-
-    auto* u0 = create<Scalar<tint::u32>>(u32, 0_u);
-    auto* u1 = create<Scalar<tint::u32>>(u32, 1_u);
-
-    auto* fPos0 = create<Scalar<tint::f32>>(f32, 0_f);
-    auto* fNeg0 = create<Scalar<tint::f32>>(f32, -0_f);
-    auto* fPos1 = create<Scalar<tint::f32>>(f32, 1_f);
-    auto* fNeg1 = create<Scalar<tint::f32>>(f32, -1_f);
-
-    auto* f16Pos0 = create<Scalar<tint::f16>>(f16, 0_h);
-    auto* f16Neg0 = create<Scalar<tint::f16>>(f16, -0_h);
-    auto* f16Pos1 = create<Scalar<tint::f16>>(f16, 1_h);
-    auto* f16Neg1 = create<Scalar<tint::f16>>(f16, -1_h);
-
-    auto* bf = create<Scalar<bool>>(bool_, false);
-    auto* bt = create<Scalar<bool>>(bool_, true);
-
-    auto* afPos0 = create<Scalar<tint::AFloat>>(f32, 0.0_a);
-    auto* afNeg0 = create<Scalar<tint::AFloat>>(f32, -0.0_a);
-    auto* afPos1 = create<Scalar<tint::AFloat>>(f32, 1.0_a);
-    auto* afNeg1 = create<Scalar<tint::AFloat>>(f32, -1.0_a);
-
-    auto* ai0 = create<Scalar<tint::AInt>>(i32, 0_a);
-    auto* aiPos1 = create<Scalar<tint::AInt>>(i32, 1_a);
-    auto* aiNeg1 = create<Scalar<tint::AInt>>(i32, -1_a);
-
-    EXPECT_TRUE(i0->AllEqual());
-    EXPECT_TRUE(iPos1->AllEqual());
-    EXPECT_TRUE(iNeg1->AllEqual());
-
-    EXPECT_TRUE(u0->AllEqual());
-    EXPECT_TRUE(u1->AllEqual());
-
-    EXPECT_TRUE(fPos0->AllEqual());
-    EXPECT_TRUE(fNeg0->AllEqual());
-    EXPECT_TRUE(fPos1->AllEqual());
-    EXPECT_TRUE(fNeg1->AllEqual());
-
-    EXPECT_TRUE(f16Pos0->AllEqual());
-    EXPECT_TRUE(f16Neg0->AllEqual());
-    EXPECT_TRUE(f16Pos1->AllEqual());
-    EXPECT_TRUE(f16Neg1->AllEqual());
-
-    EXPECT_TRUE(bf->AllEqual());
-    EXPECT_TRUE(bt->AllEqual());
-
-    EXPECT_TRUE(afPos0->AllEqual());
-    EXPECT_TRUE(afNeg0->AllEqual());
-    EXPECT_TRUE(afPos1->AllEqual());
-    EXPECT_TRUE(afNeg1->AllEqual());
-
-    EXPECT_TRUE(ai0->AllEqual());
-    EXPECT_TRUE(aiPos1->AllEqual());
-    EXPECT_TRUE(aiNeg1->AllEqual());
-}
-
 TEST_F(ConstantTest_Scalar, ValueOf) {
     auto* i32 = create<type::I32>();
     auto* u32 = create<type::U32>();
diff --git a/src/tint/constant/splat.h b/src/tint/constant/splat.h
index 5494c8f..a496235 100644
--- a/src/tint/constant/splat.h
+++ b/src/tint/constant/splat.h
@@ -25,14 +25,14 @@
 /// Splat holds a single value, duplicated as all children.
 ///
 /// Splat is used for zero-initializers, 'splat' initializers, or initializers where each element is
-/// identical. Splat may be of a vector, matrix or array type.
-class Splat : public Castable<Splat, constant::Value> {
+/// identical. Splat may be of a vector, matrix, array or structure type.
+class Splat : public Castable<Splat, Value> {
   public:
     /// Constructor
     /// @param t the splat type
     /// @param e the splat element
     /// @param n the number of items in the splat
-    Splat(const type::Type* t, const constant::Value* e, size_t n);
+    Splat(const type::Type* t, const Value* e, size_t n);
     ~Splat() override;
 
     /// @returns the type of the splat
@@ -41,14 +41,15 @@
     /// Retrieve item at index @p i
     /// @param i the index to retrieve
     /// @returns the element, or nullptr if out of bounds
-    const constant::Value* Index(size_t i) const override { return i < count ? el : nullptr; }
+    const Value* Index(size_t i) const override { return i < count ? el : nullptr; }
+
+    /// @copydoc Value::NumElements()
+    size_t NumElements() const override { return count; }
 
     /// @returns true if the element is zero
     bool AllZero() const override { return el->AllZero(); }
     /// @returns true if the element is zero
     bool AnyZero() const override { return el->AnyZero(); }
-    /// @returns true
-    bool AllEqual() const override { return true; }
 
     /// @returns the hash for the splat
     size_t Hash() const override { return utils::Hash(type, el->Hash(), count); }
@@ -61,7 +62,7 @@
     /// The type of the splat element
     type::Type const* const type;
     /// The element stored in the splat
-    const constant::Value* el;
+    const Value* el;
     /// The number of items in the splat
     const size_t count;
 
diff --git a/src/tint/constant/splat_test.cc b/src/tint/constant/splat_test.cc
index 5c897cb..fe8aeda 100644
--- a/src/tint/constant/splat_test.cc
+++ b/src/tint/constant/splat_test.cc
@@ -56,22 +56,6 @@
     EXPECT_FALSE(SpfPos1->AnyZero());
 }
 
-TEST_F(ConstantTest_Splat, AllEqual) {
-    auto* f32 = create<type::F32>();
-
-    auto* fPos0 = create<Scalar<tint::f32>>(f32, 0_f);
-    auto* fNeg0 = create<Scalar<tint::f32>>(f32, -0_f);
-    auto* fPos1 = create<Scalar<tint::f32>>(f32, 1_f);
-
-    auto* SpfPos0 = create<Splat>(f32, fPos0, 2);
-    auto* SpfNeg0 = create<Splat>(f32, fNeg0, 2);
-    auto* SpfPos1 = create<Splat>(f32, fPos1, 2);
-
-    EXPECT_TRUE(SpfPos0->AllEqual());
-    EXPECT_TRUE(SpfNeg0->AllEqual());
-    EXPECT_TRUE(SpfPos1->AllEqual());
-}
-
 TEST_F(ConstantTest_Splat, Index) {
     auto* f32 = create<type::F32>();
 
diff --git a/src/tint/constant/value.cc b/src/tint/constant/value.cc
index c41ca34..7545731 100644
--- a/src/tint/constant/value.cc
+++ b/src/tint/constant/value.cc
@@ -14,6 +14,7 @@
 
 #include "src/tint/constant/value.h"
 
+#include "src/tint/switch.h"
 #include "src/tint/type/array.h"
 #include "src/tint/type/matrix.h"
 #include "src/tint/type/struct.h"
diff --git a/src/tint/constant/value.h b/src/tint/constant/value.h
index d5b9876..eb29998 100644
--- a/src/tint/constant/value.h
+++ b/src/tint/constant/value.h
@@ -37,6 +37,7 @@
     /// @returns the type of the value
     virtual const type::Type* Type() const = 0;
 
+    /// @param i the index of the element
     /// @returns the child element with the given index, or nullptr if there are no children, or
     /// the index is out of bounds.
     ///
@@ -44,7 +45,10 @@
     /// For vectors, this returns the i'th element of the vector.
     /// For matrices, this returns the i'th column vector of the matrix.
     /// For structures, this returns the i'th member field of the structure.
-    virtual const Value* Index(size_t) const = 0;
+    virtual const Value* Index(size_t i) const = 0;
+
+    /// @return the number of elements held by this Value
+    virtual size_t NumElements() const = 0;
 
     /// @returns true if child elements are positive-zero valued.
     virtual bool AllZero() const = 0;
@@ -52,9 +56,6 @@
     /// @returns true if any child elements are positive-zero valued.
     virtual bool AnyZero() const = 0;
 
-    /// @returns true if all child elements have the same value and type.
-    virtual bool AllEqual() const = 0;
-
     /// @returns a hash of the value.
     virtual size_t Hash() const = 0;
 
@@ -74,7 +75,7 @@
 
     /// @param b the value to compare too
     /// @returns true if this value is equal to @p b
-    bool Equal(const constant::Value* b) const;
+    bool Equal(const Value* b) const;
 
     /// Clones the constant into the provided context
     /// @param ctx the clone context
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
index 6e96523..93e5c58 100644
--- a/src/tint/inspector/inspector.cc
+++ b/src/tint/inspector/inspector.cc
@@ -39,6 +39,7 @@
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/struct.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/array.h"
 #include "src/tint/type/bool.h"
 #include "src/tint/type/depth_multisampled_texture.h"
@@ -570,8 +571,10 @@
     // Ast nodes for enable directive are stored within global declarations list
     auto global_decls = program_->AST().GlobalDeclarations();
     for (auto* node : global_decls) {
-        if (auto* ext = node->As<ast::Enable>()) {
-            result.push_back({utils::ToString(ext->extension), ext->source});
+        if (auto* enable = node->As<ast::Enable>()) {
+            for (auto* ext : enable->extensions) {
+                result.push_back({utils::ToString(ext->name), ext->source});
+            }
         }
     }
 
diff --git a/src/tint/intrinsics.def b/src/tint/intrinsics.def
index 316d224..fae4ee4 100644
--- a/src/tint/intrinsics.def
+++ b/src/tint/intrinsics.def
@@ -739,7 +739,9 @@
 @must_use fn textureGatherCompare(texture: texture_depth_cube, sampler: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> vec4<f32>
 @must_use fn textureGatherCompare<A: iu32>(texture: texture_depth_cube_array, sampler: sampler_comparison, coords: vec3<f32>, array_index: A, depth_ref: f32) -> vec4<f32>
 @must_use fn textureNumLayers<T: fiu32>(texture: texture_2d_array<T>) -> u32
+@must_use fn textureNumLayers<T: fiu32>(texture: texture_cube_array<T>) -> u32
 @must_use fn textureNumLayers(texture: texture_depth_2d_array) -> u32
+@must_use fn textureNumLayers(texture: texture_depth_cube_array) -> u32
 @must_use fn textureNumLayers<F: texel_format, A: write>(texture: texture_storage_2d_array<F, A>) -> u32
 @must_use fn textureNumLevels<T: fiu32>(texture: texture_1d<T>) -> u32
 @must_use fn textureNumLevels<T: fiu32>(texture: texture_2d<T>) -> u32
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 1452dc4..a3d2a31 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -177,4 +177,26 @@
     return ir.instructions.Create<ir::Bitcast>(Temp(type), val);
 }
 
+ir::UserCall* Builder::UserCall(const type::Type* type,
+                                Symbol name,
+                                utils::VectorRef<Value*> args) {
+    return ir.instructions.Create<ir::UserCall>(Temp(type), name, std::move(args));
+}
+
+ir::Convert* Builder::Convert(const type::Type* to,
+                              const type::Type* from,
+                              utils::VectorRef<Value*> args) {
+    return ir.instructions.Create<ir::Convert>(Temp(to), from, std::move(args));
+}
+
+ir::Construct* Builder::Construct(const type::Type* to, utils::VectorRef<Value*> args) {
+    return ir.instructions.Create<ir::Construct>(Temp(to), std::move(args));
+}
+
+ir::Builtin* Builder::Builtin(const type::Type* type,
+                              builtin::Function func,
+                              utils::VectorRef<Value*> args) {
+    return ir.instructions.Create<ir::Builtin>(Temp(type), func, args);
+}
+
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index 147e4c4..5a39dbb 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -20,7 +20,10 @@
 #include "src/tint/constant/scalar.h"
 #include "src/tint/ir/binary.h"
 #include "src/tint/ir/bitcast.h"
+#include "src/tint/ir/builtin.h"
 #include "src/tint/ir/constant.h"
+#include "src/tint/ir/construct.h"
+#include "src/tint/ir/convert.h"
 #include "src/tint/ir/function.h"
 #include "src/tint/ir/if.h"
 #include "src/tint/ir/loop.h"
@@ -28,6 +31,7 @@
 #include "src/tint/ir/switch.h"
 #include "src/tint/ir/temp.h"
 #include "src/tint/ir/terminator.h"
+#include "src/tint/ir/user_call.h"
 #include "src/tint/ir/value.h"
 #include "src/tint/type/bool.h"
 #include "src/tint/type/f16.h"
@@ -279,6 +283,37 @@
     /// @returns the instruction
     ir::Bitcast* Bitcast(const type::Type* type, Value* val);
 
+    /// Creates a user function call instruction
+    /// @param type the return type of the call
+    /// @param name the name of the function being called
+    /// @param args the call arguments
+    /// @returns the instruction
+    ir::UserCall* UserCall(const type::Type* type, Symbol name, utils::VectorRef<Value*> args);
+
+    /// Creates a value conversion instruction
+    /// @param to the type converted to
+    /// @param from the type converted from
+    /// @param args the arguments to be converted
+    /// @returns the instruction
+    ir::Convert* Convert(const type::Type* to,
+                         const type::Type* from,
+                         utils::VectorRef<Value*> args);
+
+    /// Creates a value constructor instruction
+    /// @param to the type being converted
+    /// @param args the arguments to be converted
+    /// @returns the instruction
+    ir::Construct* Construct(const type::Type* to, utils::VectorRef<Value*> args);
+
+    /// Creates a builtin call instruction
+    /// @param type the return type
+    /// @param func the builtin function
+    /// @param args the arguments to be converted
+    /// @returns the instruction
+    ir::Builtin* Builtin(const type::Type* type,
+                         builtin::Function func,
+                         utils::VectorRef<Value*> args);
+
     /// @returns a unique temp id
     Temp::Id AllocateTempId();
 
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index d3f8d53..ba63be6 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -14,6 +14,8 @@
 
 #include "src/tint/ir/builder_impl.h"
 
+#include <iostream>
+
 #include "src/tint/ast/alias.h"
 #include "src/tint/ast/binary_expression.h"
 #include "src/tint/ast/bitcast_expression.h"
@@ -21,6 +23,8 @@
 #include "src/tint/ast/bool_literal_expression.h"
 #include "src/tint/ast/break_if_statement.h"
 #include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/call_expression.h"
+#include "src/tint/ast/call_statement.h"
 #include "src/tint/ast/const_assert.h"
 #include "src/tint/ast/continue_statement.h"
 #include "src/tint/ast/float_literal_expression.h"
@@ -28,6 +32,7 @@
 #include "src/tint/ast/function.h"
 #include "src/tint/ast/id_attribute.h"
 #include "src/tint/ast/identifier.h"
+#include "src/tint/ast/identifier_expression.h"
 #include "src/tint/ast/if_statement.h"
 #include "src/tint/ast/int_literal_expression.h"
 #include "src/tint/ast/literal_expression.h"
@@ -39,6 +44,7 @@
 #include "src/tint/ast/struct_member_align_attribute.h"
 #include "src/tint/ast/struct_member_size_attribute.h"
 #include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/templated_identifier.h"
 #include "src/tint/ast/variable_decl_statement.h"
 #include "src/tint/ast/while_statement.h"
 #include "src/tint/ir/function.h"
@@ -48,9 +54,15 @@
 #include "src/tint/ir/switch.h"
 #include "src/tint/ir/terminator.h"
 #include "src/tint/program.h"
+#include "src/tint/sem/builtin.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/materialize.h"
 #include "src/tint/sem/module.h"
 #include "src/tint/sem/switch_statement.h"
+#include "src/tint/sem/value_constructor.h"
+#include "src/tint/sem/value_conversion.h"
 #include "src/tint/sem/value_expression.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/void.h"
 
 namespace tint::ir {
@@ -99,6 +111,10 @@
 
 BuilderImpl::~BuilderImpl() = default;
 
+void BuilderImpl::add_error(const Source& s, const std::string& err) {
+    diagnostics_.add_error(tint::diag::System::IR, err, s);
+}
+
 void BuilderImpl::BranchTo(FlowNode* node, utils::VectorRef<Value*> args) {
     TINT_ASSERT(IR, current_flow_block);
     TINT_ASSERT(IR, !IsBranched(current_flow_block));
@@ -161,9 +177,7 @@
                 return true;
             },
             [&](Default) {
-                diagnostics_.add_warning(tint::diag::System::IR,
-                                         "unknown type: " + std::string(decl->TypeInfo().name),
-                                         decl->source);
+                add_error(decl->source, "unknown type: " + std::string(decl->TypeInfo().name));
                 return true;
             });
         if (!ok) {
@@ -237,9 +251,7 @@
         [&](const ast::BlockStatement* b) { return EmitBlock(b); },
         [&](const ast::BreakStatement* b) { return EmitBreak(b); },
         [&](const ast::BreakIfStatement* b) { return EmitBreakIf(b); },
-        // [&](const ast::CallStatement* c) {
-        // TODO(dsinclair): Implement
-        // },
+        [&](const ast::CallStatement* c) { return EmitCall(c); },
         // [&](const ast::CompoundAssignmentStatement* c) {
         // TODO(dsinclair): Implement
         // },
@@ -258,9 +270,8 @@
             return true;  // Not emitted
         },
         [&](Default) {
-            diagnostics_.add_warning(
-                tint::diag::System::IR,
-                "unknown statement type: " + std::string(stmt->TypeInfo().name), stmt->source);
+            add_error(stmt->source,
+                      "unknown statement type: " + std::string(stmt->TypeInfo().name));
             // TODO(dsinclair): This should return `false`, switch back when all
             // the cases are handled.
             return true;
@@ -593,9 +604,7 @@
         // },
         [&](const ast::BinaryExpression* b) { return EmitBinary(b); },
         [&](const ast::BitcastExpression* b) { return EmitBitcast(b); },
-        // [&](const ast::CallExpression* c) {
-        // TODO(dsinclair): Implement
-        // },
+        [&](const ast::CallExpression* c) { return EmitCall(c); },
         // [&](const ast::IdentifierExpression* i) {
         // TODO(dsinclair): Implement
         // },
@@ -610,9 +619,8 @@
         // TODO(dsinclair): Implement
         // },
         [&](Default) {
-            diagnostics_.add_warning(
-                tint::diag::System::IR,
-                "unknown expression type: " + std::string(expr->TypeInfo().name), expr->source);
+            add_error(expr->source,
+                      "unknown expression type: " + std::string(expr->TypeInfo().name));
             // TODO(dsinclair): This should return utils::Failure; Switch back
             // once all the above cases are handled.
             auto* v = builder.ir.types.Get<type::Void>();
@@ -630,19 +638,16 @@
         // TODO(dsinclair): Implement
         // },
         [&](const ast::Override*) {
-            diagnostics_.add_warning(tint::diag::System::IR,
-                                     "found an `Override` variable. The SubstituteOverrides "
-                                     "transform must be run before converting to IR",
-                                     var->source);
+            add_error(var->source,
+                      "found an `Override` variable. The SubstituteOverrides "
+                      "transform must be run before converting to IR");
             return false;
         },
         // [&](const ast::Const* c) {
         // TODO(dsinclair): Implement
         // },
         [&](Default) {
-            diagnostics_.add_warning(tint::diag::System::IR,
-                                     "unknown variable: " + std::string(var->TypeInfo().name),
-                                     var->source);
+            add_error(var->source, "unknown variable: " + std::string(var->TypeInfo().name));
 
             // TODO(dsinclair): This should return `false`, switch back when all
             // the cases are handled.
@@ -743,22 +748,83 @@
     return instr->Result();
 }
 
+utils::Result<Value*> BuilderImpl::EmitCall(const ast::CallStatement* stmt) {
+    return EmitCall(stmt->expr);
+}
+
+utils::Result<Value*> BuilderImpl::EmitCall(const ast::CallExpression* expr) {
+    // If this is a materialized semantic node, just use the constant value.
+    if (auto* mat = program_->Sem().Get(expr)) {
+        if (mat->ConstantValue()) {
+            auto* cv = mat->ConstantValue()->Clone(clone_ctx_);
+            if (!cv) {
+                add_error(expr->source, "failed to get constant value for call " +
+                                            std::string(expr->TypeInfo().name));
+                return utils::Failure;
+            }
+            return builder.Constant(cv);
+        }
+    }
+
+    utils::Vector<Value*, 8> args;
+    args.Reserve(expr->args.Length());
+
+    // Emit the arguments
+    for (const auto* arg : expr->args) {
+        auto value = EmitExpression(arg);
+        if (!value) {
+            add_error(arg->source, "failed to convert arguments");
+            return utils::Failure;
+        }
+        args.Push(value.Get());
+    }
+
+    auto* sem = program_->Sem().Get<sem::Call>(expr);
+    if (!sem) {
+        add_error(expr->source, "failed to get semantic information for call " +
+                                    std::string(expr->TypeInfo().name));
+        return utils::Failure;
+    }
+
+    auto* ty = sem->Target()->ReturnType()->Clone(clone_ctx_.type_ctx);
+
+    Instruction* instr = nullptr;
+
+    // If this is a builtin function, emit the specific builtin value
+    if (auto* b = sem->Target()->As<sem::Builtin>()) {
+        instr = builder.Builtin(ty, b->Type(), args);
+    } else if (sem->Target()->As<sem::ValueConstructor>()) {
+        instr = builder.Construct(ty, std::move(args));
+    } else if (auto* conv = sem->Target()->As<sem::ValueConversion>()) {
+        auto* from = conv->Source()->Clone(clone_ctx_.type_ctx);
+        instr = builder.Convert(ty, from, std::move(args));
+    } else if (expr->target->identifier->Is<ast::TemplatedIdentifier>()) {
+        TINT_UNIMPLEMENTED(IR, diagnostics_) << "missing templated ident support";
+        return utils::Failure;
+    } else {
+        // Not a builtin and not a templated call, so this is a user function.
+        auto name = CloneSymbol(expr->target->identifier->symbol);
+        instr = builder.UserCall(ty, name, std::move(args));
+    }
+    if (instr == nullptr) {
+        return utils::Failure;
+    }
+    current_flow_block->instructions.Push(instr);
+    return instr->Result();
+}
+
 utils::Result<Value*> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
     auto* sem = program_->Sem().Get(lit);
     if (!sem) {
-        diagnostics_.add_error(
-            tint::diag::System::IR,
-            "Failed to get semantic information for node " + std::string(lit->TypeInfo().name),
-            lit->source);
+        add_error(lit->source, "failed to get semantic information for node " +
+                                   std::string(lit->TypeInfo().name));
         return utils::Failure;
     }
 
     auto* cv = sem->ConstantValue()->Clone(clone_ctx_);
     if (!cv) {
-        diagnostics_.add_error(
-            tint::diag::System::IR,
-            "Failed to get constant value for node " + std::string(lit->TypeInfo().name),
-            lit->source);
+        add_error(lit->source,
+                  "failed to get constant value for node " + std::string(lit->TypeInfo().name));
         return utils::Failure;
     }
     return builder.Constant(cv);
@@ -804,10 +870,9 @@
         // TODO(dsinclair): Implement
         // },
         [&](const ast::IdAttribute*) {
-            diagnostics_.add_warning(tint::diag::System::IR,
-                                     "found an `Id` attribute. The SubstituteOverrides transform "
-                                     "must be run before converting to IR",
-                                     attr->source);
+            add_error(attr->source,
+                      "found an `Id` attribute. The SubstituteOverrides transform "
+                      "must be run before converting to IR");
             return false;
         },
         [&](const ast::StructMemberSizeAttribute*) {
@@ -827,9 +892,7 @@
         // TODO(dsinclair): Implement
         // },
         [&](Default) {
-            diagnostics_.add_warning(tint::diag::System::IR,
-                                     "unknown attribute: " + std::string(attr->TypeInfo().name),
-                                     attr->source);
+            add_error(attr->source, "unknown attribute: " + std::string(attr->TypeInfo().name));
             return false;
         });
 }
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index 034ab65..2e38ef7 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -39,6 +39,8 @@
 class BlockStatement;
 class BreakIfStatement;
 class BreakStatement;
+class CallExpression;
+class CallStatement;
 class ContinueStatement;
 class Expression;
 class ForLoopStatement;
@@ -61,6 +63,9 @@
 class Switch;
 class Terminator;
 }  // namespace tint::ir
+namespace tint::sem {
+class Builtin;
+}  // namespace tint::sem
 
 namespace tint::ir {
 
@@ -165,6 +170,16 @@
     /// @returns the value storing the result if successful, utils::Failure otherwise
     utils::Result<Value*> EmitBitcast(const ast::BitcastExpression* expr);
 
+    /// Emits a call expression
+    /// @param stmt the call statement
+    /// @returns the value storing the result if successful, utils::Failure otherwise
+    utils::Result<Value*> EmitCall(const ast::CallStatement* stmt);
+
+    /// Emits a call expression
+    /// @param expr the call expression
+    /// @returns the value storing the result if successful, utils::Failure otherwise
+    utils::Result<Value*> EmitCall(const ast::CallExpression* expr);
+
     /// Emits a literal expression
     /// @param lit the literal to emit
     /// @returns true if successful, false otherwise
@@ -207,6 +222,8 @@
 
     FlowNode* FindEnclosingControl(ControlFlags flags);
 
+    void add_error(const Source& s, const std::string& err);
+
     const Program* program_ = nullptr;
 
     Symbol CloneSymbol(Symbol sym) const;
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index 9af481b..4e02875 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -1858,5 +1858,106 @@
 )");
 }
 
+TEST_F(IR_BuilderImplTest, EmitStatement_UserFunction) {
+    Func("my_func", utils::Vector{Param("p", ty.f32())}, ty.void_(), utils::Empty);
+
+    auto* stmt = CallStmt(Call("my_func", Mul(2_f, 3_f)));
+    WrapInFunction(stmt);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitStatement(stmt);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1 (f32) = 2.0 * 3.0
+%2 (void) = call(my_func, %1 (f32))
+)");
+}
+
+// TODO(dsinclair): This needs assignment in order to output correctly. The empty constructor ends
+// up materializing, so there is no expression to emit until there is a usage. When assigment is
+// implemented this can be enabled (and the output updated).
+TEST_F(IR_BuilderImplTest, DISABLED_EmitExpression_ConstructEmpty) {
+    auto* expr = vec3(ty.f32());
+    GlobalVar("i", builtin::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%1 (vec3<f32>) = construct(vec3<f32>)
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Construct) {
+    auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_f));
+    auto* expr = vec3(ty.f32(), 2_f, 3_f, i);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%2 (vec3<f32>) = construct(vec3<f32>, 2.0, 3.0, %1 (void))
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Convert) {
+    auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_i));
+    auto* expr = Call(ty.f32(), i);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%2 (f32) = convert(f32, i32, %1 (void))
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_MaterializedCall) {
+    auto* expr = Return(Call("trunc", 2.5_f));
+
+    Func("test_function", {}, ty.f32(), expr, utils::Empty);
+
+    auto r = Build();
+    ASSERT_TRUE(r) << Error();
+    auto m = r.Move();
+
+    EXPECT_EQ(Disassemble(m), R"(%bb0 = Function test_function
+  %bb1 = Block
+  Return (2.0)
+FunctionEnd
+
+)");
+}
+
+TEST_F(IR_BuilderImplTest, EmitExpression_Builtin) {
+    auto i = GlobalVar("i", builtin::AddressSpace::kPrivate, Expr(1_f));
+    auto* expr = Call("asin", i);
+    WrapInFunction(expr);
+
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
+    d.EmitBlockInstructions(b.current_flow_block->As<ir::Block>());
+    EXPECT_EQ(d.AsString(), R"(%2 (f32) = asin(%1 (void))
+)");
+}
+
 }  // namespace
 }  // namespace tint::ir
diff --git a/src/tint/ir/builtin.cc b/src/tint/ir/builtin.cc
new file mode 100644
index 0000000..e9acf66
--- /dev/null
+++ b/src/tint/ir/builtin.cc
@@ -0,0 +1,37 @@
+// Copyright 2023 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/tint/ir/builtin.h"
+#include "src/tint/debug.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Builtin);
+
+// \cond DO_NOT_DOCUMENT
+namespace tint::ir {
+
+Builtin::Builtin(Value* result, builtin::Function func, utils::VectorRef<Value*> args)
+    : Base(result, args), func_(func) {}
+
+Builtin::~Builtin() = default;
+
+utils::StringStream& Builtin::ToString(utils::StringStream& out, const SymbolTable& st) const {
+    Result()->ToString(out, st);
+    out << " = " << builtin::str(func_) << "(";
+    EmitArgs(out, st);
+    out << ")";
+    return out;
+}
+
+}  // namespace tint::ir
+// \endcond
diff --git a/src/tint/ir/builtin.h b/src/tint/ir/builtin.h
new file mode 100644
index 0000000..0385c6c
--- /dev/null
+++ b/src/tint/ir/builtin.h
@@ -0,0 +1,57 @@
+// Copyright 2023 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_TINT_IR_BUILTIN_H_
+#define SRC_TINT_IR_BUILTIN_H_
+
+#include "src/tint/builtin/function.h"
+#include "src/tint/castable.h"
+#include "src/tint/ir/call.h"
+#include "src/tint/symbol_table.h"
+#include "src/tint/type/type.h"
+#include "src/tint/utils/string_stream.h"
+
+namespace tint::ir {
+
+/// A value conversion instruction in the IR.
+class Builtin : public Castable<Builtin, Call> {
+  public:
+    /// Constructor
+    /// @param result the result value
+    /// @param func the builtin function
+    /// @param args the conversion arguments
+    Builtin(Value* result, builtin::Function func, utils::VectorRef<Value*> args);
+    Builtin(const Builtin& instr) = delete;
+    Builtin(Builtin&& instr) = delete;
+    ~Builtin() override;
+
+    Builtin& operator=(const Builtin& instr) = delete;
+    Builtin& operator=(Builtin&& instr) = delete;
+
+    /// @returns the builtin function
+    builtin::Function Func() const { return func_; }
+
+    /// Write the instruction to the given stream
+    /// @param out the stream to write to
+    /// @param st the symbol table
+    /// @returns the stream
+    utils::StringStream& ToString(utils::StringStream& out, const SymbolTable& st) const override;
+
+  private:
+    const builtin::Function func_;
+};
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_BUILTIN_H_
diff --git a/src/tint/ir/call.cc b/src/tint/ir/call.cc
new file mode 100644
index 0000000..9dda1f9
--- /dev/null
+++ b/src/tint/ir/call.cc
@@ -0,0 +1,40 @@
+// Copyright 2023 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/tint/ir/call.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Call);
+
+namespace tint::ir {
+
+Call::Call(Value* result, utils::VectorRef<Value*> args) : Base(result), args_(args) {
+    for (auto* arg : args) {
+        arg->AddUsage(this);
+    }
+}
+
+Call::~Call() = default;
+
+void Call::EmitArgs(utils::StringStream& out, const SymbolTable& st) const {
+    bool first = true;
+    for (const auto* arg : args_) {
+        if (!first) {
+            out << ", ";
+        }
+        first = false;
+        arg->ToString(out, st);
+    }
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/call.h b/src/tint/ir/call.h
new file mode 100644
index 0000000..d6ced3f
--- /dev/null
+++ b/src/tint/ir/call.h
@@ -0,0 +1,54 @@
+// Copyright 2023 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_TINT_IR_CALL_H_
+#define SRC_TINT_IR_CALL_H_
+
+#include "src/tint/castable.h"
+#include "src/tint/ir/instruction.h"
+#include "src/tint/symbol_table.h"
+#include "src/tint/type/type.h"
+#include "src/tint/utils/string_stream.h"
+
+namespace tint::ir {
+
+/// A Call instruction in the IR.
+class Call : public Castable<Call, Instruction> {
+  public:
+    /// Constructor
+    /// @param result the result value
+    /// @param args the constructor arguments
+    Call(Value* result, utils::VectorRef<Value*> args);
+    Call(const Call& instr) = delete;
+    Call(Call&& instr) = delete;
+    ~Call() override;
+
+    Call& operator=(const Call& instr) = delete;
+    Call& operator=(Call&& instr) = delete;
+
+    /// @returns the constructor arguments
+    utils::VectorRef<Value*> Args() const { return args_; }
+
+    /// Writes the call arguments to the given stream.
+    /// @param out the output stream
+    /// @param st the symbol table
+    void EmitArgs(utils::StringStream& out, const SymbolTable& st) const;
+
+  private:
+    utils::Vector<Value*, 1> args_;
+};
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_CALL_H_
diff --git a/src/tint/ir/constant.cc b/src/tint/ir/constant.cc
index da4cb36..61610b0 100644
--- a/src/tint/ir/constant.cc
+++ b/src/tint/ir/constant.cc
@@ -19,6 +19,7 @@
 #include "src/tint/constant/composite.h"
 #include "src/tint/constant/scalar.h"
 #include "src/tint/constant/splat.h"
+#include "src/tint/switch.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::Constant);
 
diff --git a/src/tint/ir/construct.cc b/src/tint/ir/construct.cc
new file mode 100644
index 0000000..27bb084
--- /dev/null
+++ b/src/tint/ir/construct.cc
@@ -0,0 +1,37 @@
+// Copyright 2023 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/tint/ir/construct.h"
+#include "src/tint/debug.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Construct);
+
+namespace tint::ir {
+
+Construct::Construct(Value* result, utils::VectorRef<Value*> args) : Base(result, args) {}
+
+Construct::~Construct() = default;
+
+utils::StringStream& Construct::ToString(utils::StringStream& out, const SymbolTable& st) const {
+    Result()->ToString(out, st);
+    out << " = construct(" << Result()->Type()->FriendlyName(st);
+    if (!Args().IsEmpty()) {
+        out << ", ";
+        EmitArgs(out, st);
+    }
+    out << ")";
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/construct.h b/src/tint/ir/construct.h
new file mode 100644
index 0000000..6f620d3
--- /dev/null
+++ b/src/tint/ir/construct.h
@@ -0,0 +1,49 @@
+// Copyright 2023 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_TINT_IR_CONSTRUCT_H_
+#define SRC_TINT_IR_CONSTRUCT_H_
+
+#include "src/tint/castable.h"
+#include "src/tint/ir/call.h"
+#include "src/tint/symbol_table.h"
+#include "src/tint/type/type.h"
+#include "src/tint/utils/string_stream.h"
+
+namespace tint::ir {
+
+/// A constructor instruction in the IR.
+class Construct : public Castable<Construct, Call> {
+  public:
+    /// Constructor
+    /// @param result the result value
+    /// @param args the constructor arguments
+    Construct(Value* result, utils::VectorRef<Value*> args);
+    Construct(const Construct& instr) = delete;
+    Construct(Construct&& instr) = delete;
+    ~Construct() override;
+
+    Construct& operator=(const Construct& instr) = delete;
+    Construct& operator=(Construct&& instr) = delete;
+
+    /// Write the instruction to the given stream
+    /// @param out the stream to write to
+    /// @param st the symbol table
+    /// @returns the stream
+    utils::StringStream& ToString(utils::StringStream& out, const SymbolTable& st) const override;
+};
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_CONSTRUCT_H_
diff --git a/src/tint/ir/convert.cc b/src/tint/ir/convert.cc
new file mode 100644
index 0000000..e845adb
--- /dev/null
+++ b/src/tint/ir/convert.cc
@@ -0,0 +1,36 @@
+// Copyright 2023 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/tint/ir/convert.h"
+#include "src/tint/debug.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Convert);
+
+namespace tint::ir {
+
+Convert::Convert(Value* result, const type::Type* from, utils::VectorRef<Value*> args)
+    : Base(result, args), from_(from) {}
+
+Convert::~Convert() = default;
+
+utils::StringStream& Convert::ToString(utils::StringStream& out, const SymbolTable& st) const {
+    Result()->ToString(out, st);
+    out << " = convert(" << Result()->Type()->FriendlyName(st) << ", " << from_->FriendlyName(st)
+        << ", ";
+    EmitArgs(out, st);
+    out << ")";
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/convert.h b/src/tint/ir/convert.h
new file mode 100644
index 0000000..5132248
--- /dev/null
+++ b/src/tint/ir/convert.h
@@ -0,0 +1,58 @@
+// Copyright 2023 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_TINT_IR_CONVERT_H_
+#define SRC_TINT_IR_CONVERT_H_
+
+#include "src/tint/castable.h"
+#include "src/tint/ir/call.h"
+#include "src/tint/symbol_table.h"
+#include "src/tint/type/type.h"
+#include "src/tint/utils/string_stream.h"
+
+namespace tint::ir {
+
+/// A value conversion instruction in the IR.
+class Convert : public Castable<Convert, Call> {
+  public:
+    /// Constructor
+    /// @param result the result value
+    /// @param from the type being converted from
+    /// @param args the conversion arguments
+    Convert(Value* result, const type::Type* from, utils::VectorRef<Value*> args);
+    Convert(const Convert& instr) = delete;
+    Convert(Convert&& instr) = delete;
+    ~Convert() override;
+
+    Convert& operator=(const Convert& instr) = delete;
+    Convert& operator=(Convert&& instr) = delete;
+
+    /// @returns the from type
+    const type::Type* From() const { return from_; }
+    /// @returns the to type
+    const type::Type* To() const { return Result()->Type(); }
+
+    /// Write the instruction to the given stream
+    /// @param out the stream to write to
+    /// @param st the symbol table
+    /// @returns the stream
+    utils::StringStream& ToString(utils::StringStream& out, const SymbolTable& st) const override;
+
+  private:
+    const type::Type* from_ = nullptr;
+};
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_CONVERT_H_
diff --git a/src/tint/ir/debug.cc b/src/tint/ir/debug.cc
index 290aaeb8..eb7d40f 100644
--- a/src/tint/ir/debug.cc
+++ b/src/tint/ir/debug.cc
@@ -22,6 +22,7 @@
 #include "src/tint/ir/loop.h"
 #include "src/tint/ir/switch.h"
 #include "src/tint/ir/terminator.h"
+#include "src/tint/switch.h"
 #include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index 1bac411..041495d 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -19,6 +19,7 @@
 #include "src/tint/ir/loop.h"
 #include "src/tint/ir/switch.h"
 #include "src/tint/ir/terminator.h"
+#include "src/tint/switch.h"
 
 namespace tint::ir {
 namespace {
diff --git a/src/tint/ir/test_helper.h b/src/tint/ir/test_helper.h
index 9fb54cc..6e36ed3 100644
--- a/src/tint/ir/test_helper.h
+++ b/src/tint/ir/test_helper.h
@@ -40,6 +40,8 @@
     /// return the same builder without rebuilding.
     /// @return the builder
     BuilderImpl& CreateBuilder() {
+        SetResolveOnBuild(true);
+
         if (gen_) {
             return *gen_;
         }
diff --git a/src/tint/ir/user_call.cc b/src/tint/ir/user_call.cc
new file mode 100644
index 0000000..cf672cf
--- /dev/null
+++ b/src/tint/ir/user_call.cc
@@ -0,0 +1,36 @@
+// Copyright 2023 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/tint/ir/user_call.h"
+#include "src/tint/debug.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::UserCall);
+
+namespace tint::ir {
+
+UserCall::UserCall(Value* result, Symbol name, utils::VectorRef<Value*> args)
+    : Base(result, args), name_(name) {}
+
+UserCall::~UserCall() = default;
+
+utils::StringStream& UserCall::ToString(utils::StringStream& out, const SymbolTable& st) const {
+    Result()->ToString(out, st);
+    out << " = call(";
+    out << st.NameFor(name_) << ", ";
+    EmitArgs(out, st);
+    out << ")";
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/user_call.h b/src/tint/ir/user_call.h
new file mode 100644
index 0000000..2edeecb
--- /dev/null
+++ b/src/tint/ir/user_call.h
@@ -0,0 +1,56 @@
+// Copyright 2023 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_TINT_IR_USER_CALL_H_
+#define SRC_TINT_IR_USER_CALL_H_
+
+#include "src/tint/castable.h"
+#include "src/tint/ir/call.h"
+#include "src/tint/symbol_table.h"
+#include "src/tint/type/type.h"
+#include "src/tint/utils/string_stream.h"
+
+namespace tint::ir {
+
+/// A user call instruction in the IR.
+class UserCall : public Castable<UserCall, Call> {
+  public:
+    /// Constructor
+    /// @param result the result value
+    /// @param name the function name
+    /// @param args the function arguments
+    UserCall(Value* result, Symbol name, utils::VectorRef<Value*> args);
+    UserCall(const UserCall& instr) = delete;
+    UserCall(UserCall&& instr) = delete;
+    ~UserCall() override;
+
+    UserCall& operator=(const UserCall& instr) = delete;
+    UserCall& operator=(UserCall&& instr) = delete;
+
+    /// @returns the function name
+    Symbol Name() const { return name_; }
+
+    /// Write the instruction to the given stream
+    /// @param out the stream to write to
+    /// @param st the symbol table
+    /// @returns the stream
+    utils::StringStream& ToString(utils::StringStream& out, const SymbolTable& st) const override;
+
+  private:
+    Symbol name_{};
+};
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_USER_CALL_H_
diff --git a/src/tint/program.cc b/src/tint/program.cc
index 432758b..643999d 100644
--- a/src/tint/program.cc
+++ b/src/tint/program.cc
@@ -19,6 +19,7 @@
 #include "src/tint/resolver/resolver.h"
 #include "src/tint/sem/type_expression.h"
 #include "src/tint/sem/value_expression.h"
+#include "src/tint/switch.h"
 
 namespace tint {
 namespace {
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc
index c0c2ef6..99fce9e 100644
--- a/src/tint/program_builder.cc
+++ b/src/tint/program_builder.cc
@@ -21,6 +21,7 @@
 #include "src/tint/sem/type_expression.h"
 #include "src/tint/sem/value_expression.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/utils/compiler_macros.h"
 
 using namespace tint::number_suffixes;  // NOLINT
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index c84a6e0..31f566b 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -2048,20 +2048,22 @@
     }
 
     /// Adds the extension to the list of enable directives at the top of the module.
-    /// @param ext the extension to enable
+    /// @param extension the extension to enable
     /// @return an `ast::Enable` enabling the given extension.
-    const ast::Enable* Enable(builtin::Extension ext) {
-        auto* enable = create<ast::Enable>(ext);
+    const ast::Enable* Enable(builtin::Extension extension) {
+        auto* ext = create<ast::Extension>(extension);
+        auto* enable = create<ast::Enable>(utils::Vector{ext});
         AST().AddEnable(enable);
         return enable;
     }
 
     /// Adds the extension to the list of enable directives at the top of the module.
     /// @param source the enable source
-    /// @param ext the extension to enable
+    /// @param extension the extension to enable
     /// @return an `ast::Enable` enabling the given extension.
-    const ast::Enable* Enable(const Source& source, builtin::Extension ext) {
-        auto* enable = create<ast::Enable>(source, ext);
+    const ast::Enable* Enable(const Source& source, builtin::Extension extension) {
+        auto* ext = create<ast::Extension>(source, extension);
+        auto* enable = create<ast::Enable>(source, utils::Vector{ext});
         AST().AddEnable(enable);
         return enable;
     }
@@ -3130,14 +3132,16 @@
     /// @param condition the if statement condition expression
     /// @param body the if statement body
     /// @param else_stmt optional else statement
+    /// @param attributes optional attributes
     /// @returns the if statement pointer
     template <typename CONDITION>
     const ast::IfStatement* If(const Source& source,
                                CONDITION&& condition,
                                const ast::BlockStatement* body,
-                               const ElseStmt else_stmt = ElseStmt()) {
+                               const ElseStmt else_stmt = ElseStmt(),
+                               utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::IfStatement>(source, Expr(std::forward<CONDITION>(condition)), body,
-                                        else_stmt.stmt);
+                                        else_stmt.stmt, std::move(attributes));
     }
 
     /// Creates a ast::IfStatement with input condition, body, and optional
@@ -3145,13 +3149,15 @@
     /// @param condition the if statement condition expression
     /// @param body the if statement body
     /// @param else_stmt optional else statement
+    /// @param attributes optional attributes
     /// @returns the if statement pointer
     template <typename CONDITION>
     const ast::IfStatement* If(CONDITION&& condition,
                                const ast::BlockStatement* body,
-                               const ElseStmt else_stmt = ElseStmt()) {
+                               const ElseStmt else_stmt = ElseStmt(),
+                               utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::IfStatement>(Expr(std::forward<CONDITION>(condition)), body,
-                                        else_stmt.stmt);
+                                        else_stmt.stmt, std::move(attributes));
     }
 
     /// Creates an Else object.
@@ -3274,58 +3280,74 @@
         return create<ast::LoopStatement>(body, continuing);
     }
 
-    /// Creates a ast::ForLoopStatement with input body and optional initializer,
-    /// condition and continuing.
+    /// Creates a ast::ForLoopStatement with input body and optional initializer, condition,
+    /// continuing, and attributes.
     /// @param source the source information
     /// @param init the optional loop initializer
     /// @param cond the optional loop condition
     /// @param cont the optional loop continuing
     /// @param body the loop body
+    /// @param attributes optional attributes
     /// @returns the for loop statement pointer
     template <typename COND>
-    const ast::ForLoopStatement* For(const Source& source,
-                                     const ast::Statement* init,
-                                     COND&& cond,
-                                     const ast::Statement* cont,
-                                     const ast::BlockStatement* body) {
+    const ast::ForLoopStatement* For(
+        const Source& source,
+        const ast::Statement* init,
+        COND&& cond,
+        const ast::Statement* cont,
+        const ast::BlockStatement* body,
+        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
         return create<ast::ForLoopStatement>(source, init, Expr(std::forward<COND>(cond)), cont,
-                                             body);
+                                             body, std::move(attributes));
     }
 
-    /// Creates a ast::ForLoopStatement with input body and optional initializer,
-    /// condition and continuing.
+    /// Creates a ast::ForLoopStatement with input body and optional initializer, condition,
+    /// continuing, and attributes.
     /// @param init the optional loop initializer
     /// @param cond the optional loop condition
     /// @param cont the optional loop continuing
     /// @param body the loop body
+    /// @param attributes optional attributes
     /// @returns the for loop statement pointer
     template <typename COND>
-    const ast::ForLoopStatement* For(const ast::Statement* init,
-                                     COND&& cond,
-                                     const ast::Statement* cont,
-                                     const ast::BlockStatement* body) {
-        return create<ast::ForLoopStatement>(init, Expr(std::forward<COND>(cond)), cont, body);
+    const ast::ForLoopStatement* For(
+        const ast::Statement* init,
+        COND&& cond,
+        const ast::Statement* cont,
+        const ast::BlockStatement* body,
+        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
+        return create<ast::ForLoopStatement>(init, Expr(std::forward<COND>(cond)), cont, body,
+                                             std::move(attributes));
     }
 
-    /// Creates a ast::WhileStatement with input body and condition.
+    /// Creates a ast::WhileStatement with input body, condition, and optional attributes.
     /// @param source the source information
     /// @param cond the loop condition
     /// @param body the loop body
+    /// @param attributes optional attributes
     /// @returns the while statement pointer
     template <typename COND>
-    const ast::WhileStatement* While(const Source& source,
-                                     COND&& cond,
-                                     const ast::BlockStatement* body) {
-        return create<ast::WhileStatement>(source, Expr(std::forward<COND>(cond)), body);
+    const ast::WhileStatement* While(
+        const Source& source,
+        COND&& cond,
+        const ast::BlockStatement* body,
+        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
+        return create<ast::WhileStatement>(source, Expr(std::forward<COND>(cond)), body,
+                                           std::move(attributes));
     }
 
-    /// Creates a ast::WhileStatement with given condition and body.
+    /// Creates a ast::WhileStatement with input body, condition, and optional attributes.
     /// @param cond the condition
     /// @param body the loop body
+    /// @param attributes optional attributes
     /// @returns the while loop statement pointer
     template <typename COND>
-    const ast::WhileStatement* While(COND&& cond, const ast::BlockStatement* body) {
-        return create<ast::WhileStatement>(Expr(std::forward<COND>(cond)), body);
+    const ast::WhileStatement* While(
+        COND&& cond,
+        const ast::BlockStatement* body,
+        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
+        return create<ast::WhileStatement>(Expr(std::forward<COND>(cond)), body,
+                                           std::move(attributes));
     }
 
     /// Creates a ast::VariableDeclStatement for the input variable
@@ -3348,14 +3370,15 @@
     /// @param condition the condition expression initializer
     /// @param cases case statements
     /// @returns the switch statement pointer
-    template <typename ExpressionInit, typename... Cases>
+    template <typename ExpressionInit, typename... Cases, typename = DisableIfVectorLike<Cases...>>
     const ast::SwitchStatement* Switch(const Source& source,
                                        ExpressionInit&& condition,
                                        Cases&&... cases) {
         return create<ast::SwitchStatement>(
             source, Expr(std::forward<ExpressionInit>(condition)),
             utils::Vector<const ast::CaseStatement*, sizeof...(cases)>{
-                std::forward<Cases>(cases)...});
+                std::forward<Cases>(cases)...},
+            utils::Empty);
     }
 
     /// Creates a ast::SwitchStatement with input expression and cases
@@ -3364,12 +3387,44 @@
     /// @returns the switch statement pointer
     template <typename ExpressionInit,
               typename... Cases,
-              typename = DisableIfSource<ExpressionInit>>
+              typename = DisableIfSource<ExpressionInit>,
+              typename = DisableIfVectorLike<Cases...>>
     const ast::SwitchStatement* Switch(ExpressionInit&& condition, Cases&&... cases) {
         return create<ast::SwitchStatement>(
             Expr(std::forward<ExpressionInit>(condition)),
             utils::Vector<const ast::CaseStatement*, sizeof...(cases)>{
-                std::forward<Cases>(cases)...});
+                std::forward<Cases>(cases)...},
+            utils::Empty);
+    }
+
+    /// Creates a ast::SwitchStatement with input expression, cases, and optional attributes
+    /// @param source the source information
+    /// @param condition the condition expression initializer
+    /// @param cases case statements
+    /// @param attributes optional attributes
+    /// @returns the switch statement pointer
+    template <typename ExpressionInit>
+    const ast::SwitchStatement* Switch(
+        const Source& source,
+        ExpressionInit&& condition,
+        utils::VectorRef<const ast::CaseStatement*> cases,
+        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
+        return create<ast::SwitchStatement>(source, Expr(std::forward<ExpressionInit>(condition)),
+                                            cases, std::move(attributes));
+    }
+
+    /// Creates a ast::SwitchStatement with input expression, cases, and optional attributes
+    /// @param condition the condition expression initializer
+    /// @param cases case statements
+    /// @param attributes optional attributes
+    /// @returns the switch statement pointer
+    template <typename ExpressionInit, typename = DisableIfSource<ExpressionInit>>
+    const ast::SwitchStatement* Switch(
+        ExpressionInit&& condition,
+        utils::VectorRef<const ast::CaseStatement*> cases,
+        utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
+        return create<ast::SwitchStatement>(Expr(std::forward<ExpressionInit>(condition)), cases,
+                                            std::move(attributes));
     }
 
     /// Creates a ast::CaseStatement with input list of selectors, and body
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index 49d4e83..7ec46e7 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -32,7 +32,8 @@
 #include "src/tint/ast/unary_op_expression.h"
 #include "src/tint/ast/variable_decl_statement.h"
 #include "src/tint/builtin/builtin_value.h"
-#include "src/tint/sem/builtin_type.h"
+#include "src/tint/builtin/function.h"
+#include "src/tint/switch.h"
 #include "src/tint/transform/spirv_atomic.h"
 #include "src/tint/type/depth_texture.h"
 #include "src/tint/type/sampled_texture.h"
@@ -452,42 +453,42 @@
 }
 
 // Returns the WGSL standard library function builtin for the
-// given instruction, or sem::BuiltinType::kNone
-sem::BuiltinType GetBuiltin(spv::Op opcode) {
+// given instruction, or builtin::Function::kNone
+builtin::Function GetBuiltin(spv::Op opcode) {
     switch (opcode) {
         case spv::Op::OpBitCount:
-            return sem::BuiltinType::kCountOneBits;
+            return builtin::Function::kCountOneBits;
         case spv::Op::OpBitFieldInsert:
-            return sem::BuiltinType::kInsertBits;
+            return builtin::Function::kInsertBits;
         case spv::Op::OpBitFieldSExtract:
         case spv::Op::OpBitFieldUExtract:
-            return sem::BuiltinType::kExtractBits;
+            return builtin::Function::kExtractBits;
         case spv::Op::OpBitReverse:
-            return sem::BuiltinType::kReverseBits;
+            return builtin::Function::kReverseBits;
         case spv::Op::OpDot:
-            return sem::BuiltinType::kDot;
+            return builtin::Function::kDot;
         case spv::Op::OpDPdx:
-            return sem::BuiltinType::kDpdx;
+            return builtin::Function::kDpdx;
         case spv::Op::OpDPdy:
-            return sem::BuiltinType::kDpdy;
+            return builtin::Function::kDpdy;
         case spv::Op::OpFwidth:
-            return sem::BuiltinType::kFwidth;
+            return builtin::Function::kFwidth;
         case spv::Op::OpDPdxFine:
-            return sem::BuiltinType::kDpdxFine;
+            return builtin::Function::kDpdxFine;
         case spv::Op::OpDPdyFine:
-            return sem::BuiltinType::kDpdyFine;
+            return builtin::Function::kDpdyFine;
         case spv::Op::OpFwidthFine:
-            return sem::BuiltinType::kFwidthFine;
+            return builtin::Function::kFwidthFine;
         case spv::Op::OpDPdxCoarse:
-            return sem::BuiltinType::kDpdxCoarse;
+            return builtin::Function::kDpdxCoarse;
         case spv::Op::OpDPdyCoarse:
-            return sem::BuiltinType::kDpdyCoarse;
+            return builtin::Function::kDpdyCoarse;
         case spv::Op::OpFwidthCoarse:
-            return sem::BuiltinType::kFwidthCoarse;
+            return builtin::Function::kFwidthCoarse;
         default:
             break;
     }
-    return sem::BuiltinType::kNone;
+    return builtin::Function::kNone;
 }
 
 // @param opcode a SPIR-V opcode
@@ -705,8 +706,8 @@
         auto reversed_cases = cases;
         std::reverse(reversed_cases.begin(), reversed_cases.end());
 
-        return builder->create<ast::SwitchStatement>(Source{}, condition,
-                                                     std::move(reversed_cases));
+        return builder->create<ast::SwitchStatement>(Source{}, condition, std::move(reversed_cases),
+                                                     utils::Empty);
     }
 
     /// Switch statement condition
@@ -725,7 +726,7 @@
     /// @param builder the program builder
     /// @returns the built ast::IfStatement
     const ast::IfStatement* Build(ProgramBuilder* builder) const override {
-        return builder->create<ast::IfStatement>(Source{}, cond, body, else_stmt);
+        return builder->create<ast::IfStatement>(Source{}, cond, body, else_stmt, utils::Empty);
     }
 
     /// If-statement condition
@@ -3324,7 +3325,8 @@
         else_block = create<ast::BlockStatement>(StatementList{else_stmt}, utils::Empty);
     }
 
-    auto* if_stmt = create<ast::IfStatement>(Source{}, condition, if_block, else_block);
+    auto* if_stmt =
+        create<ast::IfStatement>(Source{}, condition, if_block, else_block, utils::Empty);
 
     return if_stmt;
 }
@@ -3816,7 +3818,7 @@
     }
 
     const auto builtin = GetBuiltin(op);
-    if (builtin != sem::BuiltinType::kNone) {
+    if (builtin != builtin::Function::kNone) {
         return MakeBuiltinCall(inst);
     }
 
@@ -5250,7 +5252,7 @@
 
 TypedExpression FunctionEmitter::MakeBuiltinCall(const spvtools::opt::Instruction& inst) {
     const auto builtin = GetBuiltin(opcode(inst));
-    auto* name = sem::str(builtin);
+    auto* name = builtin::str(builtin);
     auto* ident = create<ast::Identifier>(Source{}, builder_.Symbols().Register(name));
 
     ExpressionList params;
@@ -5741,7 +5743,7 @@
 }
 
 bool FunctionEmitter::EmitAtomicOp(const spvtools::opt::Instruction& inst) {
-    auto emit_atomic = [&](sem::BuiltinType builtin, std::initializer_list<TypedExpression> args) {
+    auto emit_atomic = [&](builtin::Function builtin, std::initializer_list<TypedExpression> args) {
         // Split args into params and expressions
         ParameterList params;
         params.Reserve(args.size());
@@ -5763,7 +5765,7 @@
 
         // Emit stub, will be removed by transform::SpirvAtomic
         auto* stub = builder_.Func(
-            Source{}, builder_.Symbols().New(std::string("stub_") + sem::str(builtin)),
+            Source{}, builder_.Symbols().New(std::string("stub_") + builtin::str(builtin)),
             std::move(params), ret_type,
             /* body */ nullptr,
             utils::Vector{
@@ -5800,39 +5802,39 @@
 
     switch (opcode(inst)) {
         case spv::Op::OpAtomicLoad:
-            return emit_atomic(sem::BuiltinType::kAtomicLoad, {oper(/*ptr*/ 0)});
+            return emit_atomic(builtin::Function::kAtomicLoad, {oper(/*ptr*/ 0)});
         case spv::Op::OpAtomicStore:
-            return emit_atomic(sem::BuiltinType::kAtomicStore,
+            return emit_atomic(builtin::Function::kAtomicStore,
                                {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicExchange:
-            return emit_atomic(sem::BuiltinType::kAtomicExchange,
+            return emit_atomic(builtin::Function::kAtomicExchange,
                                {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicCompareExchange:
         case spv::Op::OpAtomicCompareExchangeWeak:
-            return emit_atomic(sem::BuiltinType::kAtomicCompareExchangeWeak,
+            return emit_atomic(builtin::Function::kAtomicCompareExchangeWeak,
                                {oper(/*ptr*/ 0), /*value*/ oper(5), /*comparator*/ oper(4)});
         case spv::Op::OpAtomicIIncrement:
-            return emit_atomic(sem::BuiltinType::kAtomicAdd, {oper(/*ptr*/ 0), lit(1)});
+            return emit_atomic(builtin::Function::kAtomicAdd, {oper(/*ptr*/ 0), lit(1)});
         case spv::Op::OpAtomicIDecrement:
-            return emit_atomic(sem::BuiltinType::kAtomicSub, {oper(/*ptr*/ 0), lit(1)});
+            return emit_atomic(builtin::Function::kAtomicSub, {oper(/*ptr*/ 0), lit(1)});
         case spv::Op::OpAtomicIAdd:
-            return emit_atomic(sem::BuiltinType::kAtomicAdd, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(builtin::Function::kAtomicAdd, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicISub:
-            return emit_atomic(sem::BuiltinType::kAtomicSub, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(builtin::Function::kAtomicSub, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicSMin:
-            return emit_atomic(sem::BuiltinType::kAtomicMin, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(builtin::Function::kAtomicMin, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicUMin:
-            return emit_atomic(sem::BuiltinType::kAtomicMin, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(builtin::Function::kAtomicMin, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicSMax:
-            return emit_atomic(sem::BuiltinType::kAtomicMax, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(builtin::Function::kAtomicMax, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicUMax:
-            return emit_atomic(sem::BuiltinType::kAtomicMax, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(builtin::Function::kAtomicMax, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicAnd:
-            return emit_atomic(sem::BuiltinType::kAtomicAnd, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(builtin::Function::kAtomicAnd, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicOr:
-            return emit_atomic(sem::BuiltinType::kAtomicOr, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(builtin::Function::kAtomicOr, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicXor:
-            return emit_atomic(sem::BuiltinType::kAtomicXor, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
+            return emit_atomic(builtin::Function::kAtomicXor, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
         case spv::Op::OpAtomicFlagTestAndSet:
         case spv::Op::OpAtomicFlagClear:
         case spv::Op::OpAtomicFMinEXT:
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index a1c9042..9f5afdb 100644
--- a/src/tint/reader/spirv/parser_impl.cc
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -26,6 +26,7 @@
 #include "src/tint/ast/interpolate_attribute.h"
 #include "src/tint/ast/unary_op_expression.h"
 #include "src/tint/reader/spirv/function.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/depth_texture.h"
 #include "src/tint/type/multisampled_texture.h"
 #include "src/tint/type/sampled_texture.h"
diff --git a/src/tint/reader/spirv/parser_impl_barrier_test.cc b/src/tint/reader/spirv/parser_impl_barrier_test.cc
index a594098..ef8e5af 100644
--- a/src/tint/reader/spirv/parser_impl_barrier_test.cc
+++ b/src/tint/reader/spirv/parser_impl_barrier_test.cc
@@ -73,7 +73,7 @@
     ASSERT_NE(sem_call, nullptr);
     auto* builtin = sem_call->Target()->As<sem::Builtin>();
     ASSERT_NE(builtin, nullptr);
-    EXPECT_EQ(builtin->Type(), sem::BuiltinType::kWorkgroupBarrier);
+    EXPECT_EQ(builtin->Type(), builtin::Function::kWorkgroupBarrier);
 }
 
 TEST_F(SpvParserTest, StorageBarrier) {
@@ -106,7 +106,7 @@
     ASSERT_NE(sem_call, nullptr);
     auto* builtin = sem_call->Target()->As<sem::Builtin>();
     ASSERT_NE(builtin, nullptr);
-    EXPECT_EQ(builtin->Type(), sem::BuiltinType::kStorageBarrier);
+    EXPECT_EQ(builtin->Type(), builtin::Function::kStorageBarrier);
 }
 
 TEST_F(SpvParserTest, ErrBarrierInvalidExecution) {
diff --git a/src/tint/reader/spirv/parser_impl_test_helper.cc b/src/tint/reader/spirv/parser_impl_test_helper.cc
index 754922c..2a19c3d 100644
--- a/src/tint/reader/spirv/parser_impl_test_helper.cc
+++ b/src/tint/reader/spirv/parser_impl_test_helper.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/tint/reader/spirv/parser_impl_test_helper.h"
+#include "src/tint/switch.h"
 #include "src/tint/utils/string_stream.h"
 #include "src/tint/writer/wgsl/generator_impl.h"
 
diff --git a/src/tint/reader/spirv/parser_type.cc b/src/tint/reader/spirv/parser_type.cc
index 71482ff..bc8bd93 100644
--- a/src/tint/reader/spirv/parser_type.cc
+++ b/src/tint/reader/spirv/parser_type.cc
@@ -19,6 +19,7 @@
 #include <utility>
 
 #include "src/tint/program_builder.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/utils/hash.h"
 #include "src/tint/utils/map.h"
diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc
index 28fb135..91c2993 100644
--- a/src/tint/reader/wgsl/lexer.cc
+++ b/src/tint/reader/wgsl/lexer.cc
@@ -25,6 +25,7 @@
 #include <type_traits>
 #include <utility>
 
+#include "absl/strings/charconv.h"
 #include "src/tint/debug.h"
 #include "src/tint/number.h"
 #include "src/tint/text/unicode.h"
@@ -352,9 +353,11 @@
 
     // Parse the exponent if one exists
     bool has_exponent = false;
+    bool negative_exponent = false;
     if (end < length() && (matches(end, 'e') || matches(end, 'E'))) {
         end++;
         if (end < length() && (matches(end, '+') || matches(end, '-'))) {
+            negative_exponent = matches(end, '-');
             end++;
         }
 
@@ -374,10 +377,8 @@
     bool has_f_suffix = false;
     bool has_h_suffix = false;
     if (end < length() && matches(end, 'f')) {
-        end++;
         has_f_suffix = true;
     } else if (end < length() && matches(end, 'h')) {
-        end++;
         has_h_suffix = true;
     }
 
@@ -386,29 +387,51 @@
         return {};
     }
 
-    advance(end - start);
-    end_source(source);
+    // Note, the `at` method will return a static `0` if the provided position is >= length. We
+    // actually need the end pointer to point to the correct memory location to use `from_chars`.
+    // So, handle the case where we point past the length specially.
+    auto* end_ptr = &at(end);
+    if (end >= length()) {
+        end_ptr = &at(length() - 1) + 1;
+    }
 
-    double value = std::strtod(&at(start), nullptr);
+    double value = 0;
+    auto ret = absl::from_chars(&at(start), end_ptr, value);
+    bool overflow = ret.ec != std::errc();
+
+    // The provided value did not fit in a double and has a negative exponent, so treat it as a 0.
+    if (negative_exponent && ret.ec == std::errc::result_out_of_range) {
+        overflow = false;
+        value = 0.0;
+    }
+
+    TINT_ASSERT(Reader, end_ptr == ret.ptr);
+    advance(end - start);
 
     if (has_f_suffix) {
-        if (auto f = CheckedConvert<f32>(AFloat(value))) {
+        auto f = CheckedConvert<f32>(AFloat(value));
+        if (!overflow && f) {
+            advance(1);
+            end_source(source);
             return {Token::Type::kFloatLiteral_F, source, static_cast<double>(f.Get())};
-        } else {
-            return {Token::Type::kError, source, "value cannot be represented as 'f32'"};
         }
+        return {Token::Type::kError, source, "value cannot be represented as 'f32'"};
     }
 
     if (has_h_suffix) {
-        if (auto f = CheckedConvert<f16>(AFloat(value))) {
+        auto f = CheckedConvert<f16>(AFloat(value));
+        if (!overflow && f) {
+            advance(1);
+            end_source(source);
             return {Token::Type::kFloatLiteral_H, source, static_cast<double>(f.Get())};
-        } else {
-            return {Token::Type::kError, source, "value cannot be represented as 'f16'"};
         }
+        return {Token::Type::kError, source, "value cannot be represented as 'f16'"};
     }
 
+    end_source(source);
+
     TINT_BEGIN_DISABLE_WARNING(FLOAT_EQUAL);
-    if (value == HUGE_VAL || -value == HUGE_VAL) {
+    if (overflow || value == HUGE_VAL || -value == HUGE_VAL) {
         return {Token::Type::kError, source, "value cannot be represented as 'abstract-float'"};
     } else {
         return {Token::Type::kFloatLiteral, source, value};
diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc
index 95b43a7..a244273 100644
--- a/src/tint/reader/wgsl/lexer_test.cc
+++ b/src/tint/reader/wgsl/lexer_test.cc
@@ -397,7 +397,7 @@
     Lexer l(&file);
 
     auto list = l.Lex();
-    ASSERT_EQ(2u, list.size());
+    ASSERT_EQ(2u, list.size()) << "Got: " << list[0].to_str();
 
     {
         auto& t = list[0];
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index e4eab29..6864084 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -42,6 +42,7 @@
 #include "src/tint/type/multisampled_texture.h"
 #include "src/tint/type/sampled_texture.h"
 #include "src/tint/type/texture_dimension.h"
+#include "src/tint/utils/defer.h"
 #include "src/tint/utils/reverse.h"
 #include "src/tint/utils/string.h"
 #include "src/tint/utils/string_stream.h"
@@ -72,21 +73,19 @@
 
 // https://gpuweb.github.io/gpuweb/wgsl.html#reserved-keywords
 bool is_reserved(const Token& t) {
-    return t == "CompileShader" || t == "ComputeShader" || t == "DomainShader" ||
-           t == "GeometryShader" || t == "Hullshader" || t == "NULL" || t == "Self" ||
-           t == "abstract" || t == "active" || t == "alignas" || t == "alignof" || t == "as" ||
-           t == "asm" || t == "asm_fragment" || t == "async" || t == "attribute" || t == "auto" ||
-           t == "await" || t == "become" || t == "binding_array" || t == "cast" || t == "catch" ||
-           t == "class" || t == "co_await" || t == "co_return" || t == "co_yield" ||
-           t == "coherent" || t == "column_major" || t == "common" || t == "compile" ||
-           t == "compile_fragment" || t == "concept" || t == "const_cast" || t == "consteval" ||
-           t == "constexpr" || t == "constinit" || t == "crate" || t == "debugger" ||
-           t == "decltype" || t == "delete" || t == "demote" || t == "demote_to_helper" ||
-           t == "do" || t == "dynamic_cast" || t == "enum" || t == "explicit" || t == "export" ||
-           t == "extends" || t == "extern" || t == "external" || t == "filter" || t == "final" ||
-           t == "finally" || t == "friend" || t == "from" || t == "fxgroup" || t == "get" ||
-           t == "goto" || t == "groupshared" || t == "handle" || t == "highp" || t == "impl" ||
-           t == "implements" || t == "import" || t == "inline" || t == "inout" ||
+    return t == "NULL" || t == "Self" || t == "abstract" || t == "active" || t == "alignas" ||
+           t == "alignof" || t == "as" || t == "asm" || t == "asm_fragment" || t == "async" ||
+           t == "attribute" || t == "auto" || t == "await" || t == "become" ||
+           t == "binding_array" || t == "cast" || t == "catch" || t == "class" || t == "co_await" ||
+           t == "co_return" || t == "co_yield" || t == "coherent" || t == "column_major" ||
+           t == "common" || t == "compile" || t == "compile_fragment" || t == "concept" ||
+           t == "const_cast" || t == "consteval" || t == "constexpr" || t == "constinit" ||
+           t == "crate" || t == "debugger" || t == "decltype" || t == "delete" || t == "demote" ||
+           t == "demote_to_helper" || t == "do" || t == "dynamic_cast" || t == "enum" ||
+           t == "explicit" || t == "export" || t == "extends" || t == "extern" || t == "external" ||
+           t == "filter" || t == "final" || t == "finally" || t == "friend" || t == "from" ||
+           t == "fxgroup" || t == "get" || t == "goto" || t == "groupshared" || t == "highp" ||
+           t == "impl" || t == "implements" || t == "import" || t == "inline" ||
            t == "instanceof" || t == "interface" || t == "layout" || t == "lowp" || t == "macro" ||
            t == "macro_rules" || t == "match" || t == "mediump" || t == "meta" || t == "mod" ||
            t == "module" || t == "move" || t == "mut" || t == "mutable" || t == "namespace" ||
@@ -96,8 +95,8 @@
            t == "partition" || t == "pass" || t == "patch" || t == "pixelfragment" ||
            t == "precise" || t == "precision" || t == "premerge" || t == "priv" ||
            t == "protected" || t == "pub" || t == "public" || t == "readonly" || t == "ref" ||
-           t == "regardless" || t == "register" || t == "reinterpret_cast" || t == "resource" ||
-           t == "restrict" || t == "self" || t == "set" || t == "shared" || t == "signed" ||
+           t == "regardless" || t == "register" || t == "reinterpret_cast" || t == "require" ||
+           t == "resource" || t == "restrict" || t == "self" || t == "set" || t == "shared" ||
            t == "sizeof" || t == "smooth" || t == "snorm" || t == "static" ||
            t == "static_assert" || t == "static_cast" || t == "std" || t == "subroutine" ||
            t == "super" || t == "target" || t == "template" || t == "this" || t == "thread_local" ||
@@ -391,53 +390,52 @@
     return decl;
 }
 
-// enable_directive
-//  : enable name SEMICLON
+// enable_directive :
+// | 'enable' identifier (COMMA identifier)* COMMA? SEMICOLON
 Maybe<Void> ParserImpl::enable_directive() {
-    auto decl = sync(Token::Type::kSemicolon, [&]() -> Maybe<Void> {
+    return sync(Token::Type::kSemicolon, [&]() -> Maybe<Void> {
+        MultiTokenSource decl_source(this);
         if (!match(Token::Type::kEnable)) {
             return Failure::kNoMatch;
         }
 
-        // Match the extension name.
-        auto& t = peek();
-        if (handle_error(t)) {
-            // The token might itself be an error.
-            return Failure::kErrored;
-        }
-
-        if (t.Is(Token::Type::kParenLeft)) {
+        if (peek_is(Token::Type::kParenLeft)) {
             // A common error case is writing `enable(foo);` instead of `enable foo;`.
             synchronized_ = false;
-            return add_error(t.source(), "enable directives don't take parenthesis");
+            return add_error(peek().source(), "enable directives don't take parenthesis");
         }
 
-        auto ext = expect_enum("extension", builtin::ParseExtension, builtin::kExtensionStrings);
-        if (ext.errored) {
-            return Failure::kErrored;
+        utils::Vector<const ast::Extension*, 4> extensions;
+        while (continue_parsing()) {
+            Source ext_src = peek().source();
+            auto ext =
+                expect_enum("extension", builtin::ParseExtension, builtin::kExtensionStrings);
+            if (ext.errored) {
+                return Failure::kErrored;
+            }
+            extensions.Push(create<ast::Extension>(ext_src, ext.value));
+
+            if (!match(Token::Type::kComma)) {
+                break;
+            }
+            if (peek_is(Token::Type::kSemicolon)) {
+                break;
+            }
         }
 
         if (!expect("enable directive", Token::Type::kSemicolon)) {
             return Failure::kErrored;
         }
-        builder_.AST().AddEnable(create<ast::Enable>(t.source(), ext.value));
+
+        builder_.AST().AddEnable(create<ast::Enable>(decl_source.Source(), std::move(extensions)));
         return kSuccess;
     });
-
-    if (decl.errored) {
-        return Failure::kErrored;
-    }
-    if (decl.matched) {
-        return kSuccess;
-    }
-
-    return Failure::kNoMatch;
 }
 
 // requires_directive
-//  : require identifier (COMMA identifier)? SEMICLON
+//  : require identifier (COMMA identifier)* COMMA? SEMICOLON
 Maybe<Void> ParserImpl::requires_directive() {
-    auto decl = sync(Token::Type::kSemicolon, [&]() -> Maybe<Void> {
+    return sync(Token::Type::kSemicolon, [&]() -> Maybe<Void> {
         if (!match(Token::Type::kRequires)) {
             return Failure::kNoMatch;
         }
@@ -484,15 +482,6 @@
         // conditional.
         return add_error(t.source(), "missing feature names in requires directive");
     });
-
-    if (decl.errored) {
-        return Failure::kErrored;
-    }
-    if (decl.matched) {
-        return kSuccess;
-    }
-
-    return Failure::kNoMatch;
 }
 
 // global_decl
@@ -1265,7 +1254,7 @@
         return stmt;
     }
 
-    auto stmt_if = if_statement();
+    auto stmt_if = if_statement(attrs.value);
     if (stmt_if.errored) {
         return Failure::kErrored;
     }
@@ -1273,7 +1262,7 @@
         return stmt_if.value;
     }
 
-    auto sw = switch_statement();
+    auto sw = switch_statement(attrs.value);
     if (sw.errored) {
         return Failure::kErrored;
     }
@@ -1289,7 +1278,7 @@
         return loop.value;
     }
 
-    auto stmt_for = for_statement();
+    auto stmt_for = for_statement(attrs.value);
     if (stmt_for.errored) {
         return Failure::kErrored;
     }
@@ -1297,7 +1286,7 @@
         return stmt_for.value;
     }
 
-    auto stmt_while = while_statement();
+    auto stmt_while = while_statement(attrs.value);
     if (stmt_while.errored) {
         return Failure::kErrored;
     }
@@ -1517,11 +1506,14 @@
 }
 
 // if_statement
-//   : IF expression compound_stmt ( ELSE else_stmt ) ?
-// else_stmt
-//  : compound_statement
-//  | if_statement
-Maybe<const ast::IfStatement*> ParserImpl::if_statement() {
+//   : attribute* if_clause else_if_clause* else_clause?
+// if_clause:
+//   : IF expression compound_stmt
+// else_if_clause:
+//   : ELSE IF expression compound_stmt
+// else_clause
+//   : ELSE compound_statement
+Maybe<const ast::IfStatement*> ParserImpl::if_statement(AttributeList& attrs) {
     // Parse if-else chains iteratively instead of recursively, to avoid
     // stack-overflow for long chains of if-else statements.
 
@@ -1529,6 +1521,7 @@
         Source source;
         const ast::Expression* condition;
         const ast::BlockStatement* body;
+        AttributeList attributes;
     };
 
     // Parse an if statement, capturing the source, condition, and body statement.
@@ -1551,7 +1544,8 @@
             return Failure::kErrored;
         }
 
-        return IfInfo{source, condition.value, body.value};
+        TINT_DEFER(attrs.Clear());
+        return IfInfo{source, condition.value, body.value, std::move(attrs)};
     };
 
     std::vector<IfInfo> statements;
@@ -1592,15 +1586,16 @@
 
     // Now walk back through the statements to create their AST nodes.
     for (auto itr = statements.rbegin(); itr != statements.rend(); itr++) {
-        last_stmt = create<ast::IfStatement>(itr->source, itr->condition, itr->body, last_stmt);
+        last_stmt = create<ast::IfStatement>(itr->source, itr->condition, itr->body, last_stmt,
+                                             std::move(itr->attributes));
     }
 
     return last_stmt->As<ast::IfStatement>();
 }
 
 // switch_statement
-//   : SWITCH expression BRACKET_LEFT switch_body+ BRACKET_RIGHT
-Maybe<const ast::SwitchStatement*> ParserImpl::switch_statement() {
+//   : attribute* SWITCH expression BRACKET_LEFT switch_body+ BRACKET_RIGHT
+Maybe<const ast::SwitchStatement*> ParserImpl::switch_statement(AttributeList& attrs) {
     Source source;
     if (!match(Token::Type::kSwitch, &source)) {
         return Failure::kNoMatch;
@@ -1638,7 +1633,8 @@
         return Failure::kErrored;
     }
 
-    return create<ast::SwitchStatement>(source, condition.value, body.value);
+    TINT_DEFER(attrs.Clear());
+    return create<ast::SwitchStatement>(source, condition.value, body.value, std::move(attrs));
 }
 
 // switch_body
@@ -1875,7 +1871,7 @@
 
 // for_statement
 //   : FOR PAREN_LEFT for_header PAREN_RIGHT compound_statement
-Maybe<const ast::ForLoopStatement*> ParserImpl::for_statement() {
+Maybe<const ast::ForLoopStatement*> ParserImpl::for_statement(AttributeList& attrs) {
     Source source;
     if (!match(Token::Type::kFor, &source)) {
         return Failure::kNoMatch;
@@ -1891,13 +1887,14 @@
         return Failure::kErrored;
     }
 
+    TINT_DEFER(attrs.Clear());
     return create<ast::ForLoopStatement>(source, header->initializer, header->condition,
-                                         header->continuing, body.value);
+                                         header->continuing, body.value, std::move(attrs));
 }
 
 // while_statement
-//   :  WHILE expression compound_statement
-Maybe<const ast::WhileStatement*> ParserImpl::while_statement() {
+//   :  attribute* WHILE expression compound_statement
+Maybe<const ast::WhileStatement*> ParserImpl::while_statement(AttributeList& attrs) {
     Source source;
     if (!match(Token::Type::kWhile, &source)) {
         return Failure::kNoMatch;
@@ -1916,7 +1913,8 @@
         return Failure::kErrored;
     }
 
-    return create<ast::WhileStatement>(source, condition.value, body.value);
+    TINT_DEFER(attrs.Clear());
+    return create<ast::WhileStatement>(source, condition.value, body.value, std::move(attrs));
 }
 
 // func_call_statement
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index bfc2f9a..9693ea4 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -488,12 +488,14 @@
     /// Parses a `variable_statement` grammar element
     /// @returns the parsed variable or nullptr
     Maybe<const ast::VariableDeclStatement*> variable_statement();
-    /// Parses a `if_statement` grammar element
+    /// Parses a `if_statement` grammar element, with the attribute list provided as `attrs`.
+    /// @param attrs the list of attributes for the statement
     /// @returns the parsed statement or nullptr
-    Maybe<const ast::IfStatement*> if_statement();
+    Maybe<const ast::IfStatement*> if_statement(AttributeList& attrs);
     /// Parses a `switch_statement` grammar element
+    /// @param attrs the list of attributes for the statement
     /// @returns the parsed statement or nullptr
-    Maybe<const ast::SwitchStatement*> switch_statement();
+    Maybe<const ast::SwitchStatement*> switch_statement(AttributeList& attrs);
     /// Parses a `switch_body` grammar element
     /// @returns the parsed statement or nullptr
     Maybe<const ast::CaseStatement*> switch_body();
@@ -515,12 +517,14 @@
     /// Parses a `for_header` grammar element, erroring on parse failure.
     /// @returns the parsed for header or nullptr
     Expect<std::unique_ptr<ForHeader>> expect_for_header();
-    /// Parses a `for_statement` grammar element
+    /// Parses a `for_statement` grammar element, with the attribute list provided as `attrs`.
+    /// @param attrs the list of attributes for the statement
     /// @returns the parsed for loop or nullptr
-    Maybe<const ast::ForLoopStatement*> for_statement();
-    /// Parses a `while_statement` grammar element
+    Maybe<const ast::ForLoopStatement*> for_statement(AttributeList& attrs);
+    /// Parses a `while_statement` grammar element, with the attribute list provided as `attrs`.
+    /// @param attrs the list of attributes for the statement
     /// @returns the parsed while loop or nullptr
-    Maybe<const ast::WhileStatement*> while_statement();
+    Maybe<const ast::WhileStatement*> while_statement(AttributeList& attrs);
     /// Parses a `break_if_statement` grammar element
     /// @returns the parsed statement or nullptr
     Maybe<const ast::Statement*> break_if_statement();
diff --git a/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc b/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc
index 7ad39c2..3c63a02 100644
--- a/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc
@@ -22,7 +22,7 @@
 using EnableDirectiveTest = ParserImplTest;
 
 // Test a valid enable directive.
-TEST_F(EnableDirectiveTest, Valid) {
+TEST_F(EnableDirectiveTest, Single) {
     auto p = parser("enable f16;");
     p->enable_directive();
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -30,13 +30,105 @@
     auto& ast = program.AST();
     ASSERT_EQ(ast.Enables().Length(), 1u);
     auto* enable = ast.Enables()[0];
-    EXPECT_EQ(enable->extension, builtin::Extension::kF16);
+    EXPECT_EQ(enable->source.range.begin.line, 1u);
+    EXPECT_EQ(enable->source.range.begin.column, 1u);
+    EXPECT_EQ(enable->source.range.end.line, 1u);
+    EXPECT_EQ(enable->source.range.end.column, 12u);
+    ASSERT_EQ(enable->extensions.Length(), 1u);
+    EXPECT_EQ(enable->extensions[0]->name, builtin::Extension::kF16);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.line, 1u);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.column, 8u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.line, 1u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.column, 11u);
+    ASSERT_EQ(ast.GlobalDeclarations().Length(), 1u);
+    EXPECT_EQ(ast.GlobalDeclarations()[0], enable);
+}
+
+// Test a valid enable directive.
+TEST_F(EnableDirectiveTest, SingleTrailingComma) {
+    auto p = parser("enable f16, ;");
+    p->enable_directive();
+    EXPECT_FALSE(p->has_error()) << p->error();
+    auto program = p->program();
+    auto& ast = program.AST();
+    ASSERT_EQ(ast.Enables().Length(), 1u);
+    auto* enable = ast.Enables()[0];
+    EXPECT_EQ(enable->source.range.begin.line, 1u);
+    EXPECT_EQ(enable->source.range.begin.column, 1u);
+    EXPECT_EQ(enable->source.range.end.line, 1u);
+    EXPECT_EQ(enable->source.range.end.column, 14u);
+    ASSERT_EQ(enable->extensions.Length(), 1u);
+    EXPECT_EQ(enable->extensions[0]->name, builtin::Extension::kF16);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.line, 1u);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.column, 8u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.line, 1u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.column, 11u);
+    ASSERT_EQ(ast.GlobalDeclarations().Length(), 1u);
+    EXPECT_EQ(ast.GlobalDeclarations()[0], enable);
+}
+
+// Test a valid enable directive with multiple extensions.
+TEST_F(EnableDirectiveTest, Multiple) {
+    auto p =
+        parser("enable f16, chromium_disable_uniformity_analysis, chromium_experimental_dp4a;");
+    p->enable_directive();
+    EXPECT_FALSE(p->has_error()) << p->error();
+    auto program = p->program();
+    auto& ast = program.AST();
+    ASSERT_EQ(ast.Enables().Length(), 1u);
+    auto* enable = ast.Enables()[0];
+    ASSERT_EQ(enable->extensions.Length(), 3u);
+    EXPECT_EQ(enable->extensions[0]->name, builtin::Extension::kF16);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.line, 1u);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.column, 8u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.line, 1u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.column, 11u);
+    EXPECT_EQ(enable->extensions[1]->name, builtin::Extension::kChromiumDisableUniformityAnalysis);
+    EXPECT_EQ(enable->extensions[1]->source.range.begin.line, 1u);
+    EXPECT_EQ(enable->extensions[1]->source.range.begin.column, 13u);
+    EXPECT_EQ(enable->extensions[1]->source.range.end.line, 1u);
+    EXPECT_EQ(enable->extensions[1]->source.range.end.column, 49u);
+    EXPECT_EQ(enable->extensions[2]->name, builtin::Extension::kChromiumExperimentalDp4A);
+    EXPECT_EQ(enable->extensions[2]->source.range.begin.line, 1u);
+    EXPECT_EQ(enable->extensions[2]->source.range.begin.column, 51u);
+    EXPECT_EQ(enable->extensions[2]->source.range.end.line, 1u);
+    EXPECT_EQ(enable->extensions[2]->source.range.end.column, 77u);
+    ASSERT_EQ(ast.GlobalDeclarations().Length(), 1u);
+    EXPECT_EQ(ast.GlobalDeclarations()[0], enable);
+}
+
+// Test a valid enable directive with multiple extensions.
+TEST_F(EnableDirectiveTest, MultipleTrailingComma) {
+    auto p =
+        parser("enable f16, chromium_disable_uniformity_analysis, chromium_experimental_dp4a,;");
+    p->enable_directive();
+    EXPECT_FALSE(p->has_error()) << p->error();
+    auto program = p->program();
+    auto& ast = program.AST();
+    ASSERT_EQ(ast.Enables().Length(), 1u);
+    auto* enable = ast.Enables()[0];
+    ASSERT_EQ(enable->extensions.Length(), 3u);
+    EXPECT_EQ(enable->extensions[0]->name, builtin::Extension::kF16);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.line, 1u);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.column, 8u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.line, 1u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.column, 11u);
+    EXPECT_EQ(enable->extensions[1]->name, builtin::Extension::kChromiumDisableUniformityAnalysis);
+    EXPECT_EQ(enable->extensions[1]->source.range.begin.line, 1u);
+    EXPECT_EQ(enable->extensions[1]->source.range.begin.column, 13u);
+    EXPECT_EQ(enable->extensions[1]->source.range.end.line, 1u);
+    EXPECT_EQ(enable->extensions[1]->source.range.end.column, 49u);
+    EXPECT_EQ(enable->extensions[2]->name, builtin::Extension::kChromiumExperimentalDp4A);
+    EXPECT_EQ(enable->extensions[2]->source.range.begin.line, 1u);
+    EXPECT_EQ(enable->extensions[2]->source.range.begin.column, 51u);
+    EXPECT_EQ(enable->extensions[2]->source.range.end.line, 1u);
+    EXPECT_EQ(enable->extensions[2]->source.range.end.column, 77u);
     ASSERT_EQ(ast.GlobalDeclarations().Length(), 1u);
     EXPECT_EQ(ast.GlobalDeclarations()[0], enable);
 }
 
 // Test multiple enable directives for a same extension.
-TEST_F(EnableDirectiveTest, EnableMultipleTime) {
+TEST_F(EnableDirectiveTest, EnableSameLine) {
     auto p = parser(R"(
 enable f16;
 enable f16;
@@ -48,8 +140,18 @@
     ASSERT_EQ(ast.Enables().Length(), 2u);
     auto* enable_a = ast.Enables()[0];
     auto* enable_b = ast.Enables()[1];
-    EXPECT_EQ(enable_a->extension, builtin::Extension::kF16);
-    EXPECT_EQ(enable_b->extension, builtin::Extension::kF16);
+    ASSERT_EQ(enable_a->extensions.Length(), 1u);
+    EXPECT_EQ(enable_a->extensions[0]->name, builtin::Extension::kF16);
+    EXPECT_EQ(enable_a->extensions[0]->source.range.begin.line, 2u);
+    EXPECT_EQ(enable_a->extensions[0]->source.range.begin.column, 8u);
+    EXPECT_EQ(enable_a->extensions[0]->source.range.end.line, 2u);
+    EXPECT_EQ(enable_a->extensions[0]->source.range.end.column, 11u);
+    ASSERT_EQ(enable_b->extensions.Length(), 1u);
+    EXPECT_EQ(enable_b->extensions[0]->name, builtin::Extension::kF16);
+    EXPECT_EQ(enable_b->extensions[0]->source.range.begin.line, 3u);
+    EXPECT_EQ(enable_b->extensions[0]->source.range.begin.column, 8u);
+    EXPECT_EQ(enable_b->extensions[0]->source.range.end.line, 3u);
+    EXPECT_EQ(enable_b->extensions[0]->source.range.end.column, 11u);
     ASSERT_EQ(ast.GlobalDeclarations().Length(), 2u);
     EXPECT_EQ(ast.GlobalDeclarations()[0], enable_a);
     EXPECT_EQ(ast.GlobalDeclarations()[1], enable_b);
@@ -169,7 +271,12 @@
     // Accept the enable directive although it caused an error
     ASSERT_EQ(ast.Enables().Length(), 1u);
     auto* enable = ast.Enables()[0];
-    EXPECT_EQ(enable->extension, builtin::Extension::kF16);
+    ASSERT_EQ(enable->extensions.Length(), 1u);
+    EXPECT_EQ(enable->extensions[0]->name, builtin::Extension::kF16);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.line, 3u);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.column, 8u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.line, 3u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.column, 11u);
     ASSERT_EQ(ast.GlobalDeclarations().Length(), 2u);
     EXPECT_EQ(ast.GlobalDeclarations()[1], enable);
 }
@@ -189,7 +296,12 @@
     // Accept the enable directive although it cause an error
     ASSERT_EQ(ast.Enables().Length(), 1u);
     auto* enable = ast.Enables()[0];
-    EXPECT_EQ(enable->extension, builtin::Extension::kF16);
+    ASSERT_EQ(enable->extensions.Length(), 1u);
+    EXPECT_EQ(enable->extensions[0]->name, builtin::Extension::kF16);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.line, 3u);
+    EXPECT_EQ(enable->extensions[0]->source.range.begin.column, 8u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.line, 3u);
+    EXPECT_EQ(enable->extensions[0]->source.range.end.column, 11u);
     ASSERT_EQ(ast.GlobalDeclarations().Length(), 1u);
     EXPECT_EQ(ast.GlobalDeclarations()[0], enable);
 }
diff --git a/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc
index 5af8f22..528be9f 100644
--- a/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_for_stmt_test.cc
@@ -24,7 +24,8 @@
 // Test an empty for loop.
 TEST_F(ForStmtTest, Empty) {
     auto p = parser("for (;;) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -37,7 +38,8 @@
 // Test a for loop with non-empty body.
 TEST_F(ForStmtTest, Body) {
     auto p = parser("for (;;) { discard; }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -51,7 +53,8 @@
 // Test a for loop declaring a variable in the initializer statement.
 TEST_F(ForStmtTest, InitializerStatementDecl) {
     auto p = parser("for (var i: i32 ;;) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -68,7 +71,8 @@
 // statement.
 TEST_F(ForStmtTest, InitializerStatementDeclEqual) {
     auto p = parser("for (var i: i32 = 0 ;;) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -84,7 +88,8 @@
 // Test a for loop declaring a const variable in the initializer statement.
 TEST_F(ForStmtTest, InitializerStatementConstDecl) {
     auto p = parser("for (let i: i32 = 0 ;;) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -100,7 +105,8 @@
 // Test a for loop assigning a variable in the initializer statement.
 TEST_F(ForStmtTest, InitializerStatementAssignment) {
     auto p = parser("for (i = 0 ;;) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -113,7 +119,8 @@
 // Test a for loop incrementing a variable in the initializer statement.
 TEST_F(ForStmtTest, InitializerStatementIncrement) {
     auto p = parser("for (i++;;) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -126,7 +133,8 @@
 // Test a for loop calling a function in the initializer statement.
 TEST_F(ForStmtTest, InitializerStatementFuncCall) {
     auto p = parser("for (a(b,c) ;;) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -139,7 +147,8 @@
 // Test a for loop with a break condition
 TEST_F(ForStmtTest, BreakCondition) {
     auto p = parser("for (; 0 == 1;) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -152,7 +161,8 @@
 // Test a for loop assigning a variable in the continuing statement.
 TEST_F(ForStmtTest, ContinuingAssignment) {
     auto p = parser("for (;; x = 2) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -165,7 +175,8 @@
 // Test a for loop with an increment statement as the continuing statement.
 TEST_F(ForStmtTest, ContinuingIncrement) {
     auto p = parser("for (;; x++) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -178,7 +189,8 @@
 // Test a for loop calling a function in the continuing statement.
 TEST_F(ForStmtTest, ContinuingFuncCall) {
     auto p = parser("for (;; a(b,c)) { }");
-    auto fl = p->for_statement();
+    ParserImpl::AttributeList attrs;
+    auto fl = p->for_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(fl.errored);
     ASSERT_TRUE(fl.matched);
@@ -188,11 +200,26 @@
     EXPECT_TRUE(fl->body->Empty());
 }
 
+// Test a for loop with attributes.
+TEST_F(ForStmtTest, WithAttributes) {
+    auto p = parser("@diagnostic(off, derivative_uniformity) for (;;) { }");
+    auto attrs = p->attribute_list();
+    auto fl = p->for_statement(attrs.value);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    EXPECT_FALSE(fl.errored);
+    ASSERT_TRUE(fl.matched);
+
+    EXPECT_TRUE(attrs->IsEmpty());
+    ASSERT_EQ(fl->attributes.Length(), 1u);
+    EXPECT_TRUE(fl->attributes[0]->Is<ast::DiagnosticAttribute>());
+}
+
 class ForStmtErrorTest : public ParserImplTest {
   public:
     void TestForWithError(std::string for_str, std::string error_str) {
         auto p_for = parser(for_str);
-        auto e_for = p_for->for_statement();
+        ParserImpl::AttributeList attrs;
+        auto e_for = p_for->for_statement(attrs);
 
         EXPECT_FALSE(e_for.matched);
         EXPECT_TRUE(e_for.errored);
diff --git a/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc
index 55effc7..2acb26c 100644
--- a/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_if_stmt_test.cc
@@ -19,7 +19,8 @@
 
 TEST_F(ParserImplTest, IfStmt) {
     auto p = parser("if a == 4 { a = b; c = d; }");
-    auto e = p->if_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->if_statement(attrs);
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -34,7 +35,8 @@
 
 TEST_F(ParserImplTest, IfStmt_WithElse) {
     auto p = parser("if a == 4 { a = b; c = d; } else if(c) { d = 2; } else {}");
-    auto e = p->if_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->if_statement(attrs);
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -57,7 +59,8 @@
 
 TEST_F(ParserImplTest, IfStmt_WithElse_WithParens) {
     auto p = parser("if(a==4) { a = b; c = d; } else if(c) { d = 2; } else {}");
-    auto e = p->if_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->if_statement(attrs);
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -78,9 +81,25 @@
     EXPECT_EQ(el->statements.Length(), 0u);
 }
 
+TEST_F(ParserImplTest, IfStmt_WithAttributes) {
+    auto p = parser(R"(@diagnostic(off, derivative_uniformity) if true { })");
+    auto a = p->attribute_list();
+    auto e = p->if_statement(a.value);
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+    ASSERT_TRUE(e->Is<ast::IfStatement>());
+
+    EXPECT_TRUE(a->IsEmpty());
+    ASSERT_EQ(e->attributes.Length(), 1u);
+    EXPECT_TRUE(e->attributes[0]->Is<ast::DiagnosticAttribute>());
+}
+
 TEST_F(ParserImplTest, IfStmt_InvalidCondition) {
     auto p = parser("if a = 3 {}");
-    auto e = p->if_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->if_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -90,7 +109,8 @@
 
 TEST_F(ParserImplTest, IfStmt_MissingCondition) {
     auto p = parser("if {}");
-    auto e = p->if_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->if_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -100,7 +120,8 @@
 
 TEST_F(ParserImplTest, IfStmt_InvalidBody) {
     auto p = parser("if a { fn main() {}}");
-    auto e = p->if_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->if_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -110,7 +131,8 @@
 
 TEST_F(ParserImplTest, IfStmt_MissingBody) {
     auto p = parser("if a");
-    auto e = p->if_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->if_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -120,7 +142,8 @@
 
 TEST_F(ParserImplTest, IfStmt_InvalidElseif) {
     auto p = parser("if a {} else if a { fn main() -> a{}}");
-    auto e = p->if_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->if_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -130,7 +153,8 @@
 
 TEST_F(ParserImplTest, IfStmt_InvalidElse) {
     auto p = parser("if a {} else { fn main() -> a{}}");
-    auto e = p->if_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->if_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc
index 647bb44..50f43ea 100644
--- a/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_reserved_keyword_test.cc
@@ -85,11 +85,7 @@
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplReservedKeywordTest,
                          ParserImplReservedKeywordTest,
-                         testing::Values("ComputeShader",
-                                         "DomainShader",
-                                         "GeometryShader",
-                                         "Hullshader",
-                                         "NULL",
+                         testing::Values("NULL",
                                          "Self",
                                          "abstract",
                                          "active",
@@ -143,13 +139,11 @@
                                          "get",
                                          "goto",
                                          "groupshared",
-                                         "handle",
                                          "highp",
                                          "impl",
                                          "implements",
                                          "import",
                                          "inline",
-                                         "inout",
                                          "instanceof",
                                          "interface",
                                          "layout",
@@ -198,7 +192,6 @@
                                          "self",
                                          "set",
                                          "shared",
-                                         "signed",
                                          "sizeof",
                                          "smooth",
                                          "snorm",
diff --git a/src/tint/reader/wgsl/parser_impl_statement_test.cc b/src/tint/reader/wgsl/parser_impl_statement_test.cc
index 634bfb4..8c920a7 100644
--- a/src/tint/reader/wgsl/parser_impl_statement_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_statement_test.cc
@@ -314,6 +314,66 @@
     EXPECT_EQ(sa->condition->source.range.end.column, 19u);
 }
 
+TEST_F(ParserImplTest, Statement_ConsumedAttributes_Block) {
+    auto p = parser("@diagnostic(off, derivative_uniformity) {}");
+    auto e = p->statement();
+    ASSERT_FALSE(p->has_error()) << p->error();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+
+    auto* s = As<ast::BlockStatement>(e.value);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->attributes.Length(), 1u);
+}
+
+TEST_F(ParserImplTest, Statement_ConsumedAttributes_For) {
+    auto p = parser("@diagnostic(off, derivative_uniformity) for (;false;) {}");
+    auto e = p->statement();
+    ASSERT_FALSE(p->has_error()) << p->error();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+
+    auto* s = As<ast::ForLoopStatement>(e.value);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->attributes.Length(), 1u);
+}
+
+TEST_F(ParserImplTest, Statement_ConsumedAttributes_If) {
+    auto p = parser("@diagnostic(off, derivative_uniformity) if true {}");
+    auto e = p->statement();
+    ASSERT_FALSE(p->has_error()) << p->error();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+
+    auto* s = As<ast::IfStatement>(e.value);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->attributes.Length(), 1u);
+}
+
+TEST_F(ParserImplTest, Statement_ConsumedAttributes_Switch) {
+    auto p = parser("@diagnostic(off, derivative_uniformity) switch (0) { default{} }");
+    auto e = p->statement();
+    ASSERT_FALSE(p->has_error()) << p->error();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+
+    auto* s = As<ast::SwitchStatement>(e.value);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->attributes.Length(), 1u);
+}
+
+TEST_F(ParserImplTest, Statement_ConsumedAttributes_While) {
+    auto p = parser("@diagnostic(off, derivative_uniformity) while (false) {}");
+    auto e = p->statement();
+    ASSERT_FALSE(p->has_error()) << p->error();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+
+    auto* s = As<ast::WhileStatement>(e.value);
+    ASSERT_NE(s, nullptr);
+    EXPECT_EQ(s->attributes.Length(), 1u);
+}
+
 TEST_F(ParserImplTest, Statement_UnexpectedAttributes) {
     auto p = parser("@diagnostic(off, derivative_uniformity) return;");
     auto e = p->statement();
diff --git a/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc
index a5e3dd3..ef0b1b4 100644
--- a/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_switch_stmt_test.cc
@@ -22,7 +22,8 @@
   case 1: {}
   case 2: {}
 })");
-    auto e = p->switch_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->switch_statement(attrs);
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -35,7 +36,8 @@
 
 TEST_F(ParserImplTest, SwitchStmt_Empty) {
     auto p = parser("switch a { }");
-    auto e = p->switch_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->switch_statement(attrs);
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -50,7 +52,8 @@
   default: {}
   case 2: {}
 })");
-    auto e = p->switch_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->switch_statement(attrs);
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -67,7 +70,8 @@
     auto p = parser(R"(switch a {
   case 1, default, 2: {}
 })");
-    auto e = p->switch_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->switch_statement(attrs);
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -80,7 +84,8 @@
 
 TEST_F(ParserImplTest, SwitchStmt_WithParens) {
     auto p = parser("switch(a+b) { }");
-    auto e = p->switch_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->switch_statement(attrs);
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
@@ -89,9 +94,25 @@
     ASSERT_EQ(e->body.Length(), 0u);
 }
 
+TEST_F(ParserImplTest, SwitchStmt_WithAttributes) {
+    auto p = parser(R"(@diagnostic(off, derivative_uniformity) switch a { default{} })");
+    auto a = p->attribute_list();
+    auto e = p->switch_statement(a.value);
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_NE(e.value, nullptr);
+    ASSERT_TRUE(e->Is<ast::SwitchStatement>());
+
+    EXPECT_TRUE(a->IsEmpty());
+    ASSERT_EQ(e->attributes.Length(), 1u);
+    EXPECT_TRUE(e->attributes[0]->Is<ast::DiagnosticAttribute>());
+}
+
 TEST_F(ParserImplTest, SwitchStmt_InvalidExpression) {
     auto p = parser("switch a=b {}");
-    auto e = p->switch_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->switch_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -101,7 +122,8 @@
 
 TEST_F(ParserImplTest, SwitchStmt_MissingExpression) {
     auto p = parser("switch {}");
-    auto e = p->switch_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->switch_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -111,7 +133,8 @@
 
 TEST_F(ParserImplTest, SwitchStmt_MissingBracketLeft) {
     auto p = parser("switch a }");
-    auto e = p->switch_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->switch_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -121,7 +144,8 @@
 
 TEST_F(ParserImplTest, SwitchStmt_MissingBracketRight) {
     auto p = parser("switch a {");
-    auto e = p->switch_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->switch_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
@@ -133,7 +157,8 @@
     auto p = parser(R"(switch a {
   case: {}
 })");
-    auto e = p->switch_statement();
+    ParserImpl::AttributeList attrs;
+    auto e = p->switch_statement(attrs);
     EXPECT_FALSE(e.matched);
     EXPECT_TRUE(e.errored);
     EXPECT_EQ(e.value, nullptr);
diff --git a/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc
index d61843e..dcbb59b 100644
--- a/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc
@@ -24,7 +24,8 @@
 // Test an empty while loop.
 TEST_F(WhileStmtTest, Empty) {
     auto p = parser("while true { }");
-    auto wl = p->while_statement();
+    ParserImpl::AttributeList attrs;
+    auto wl = p->while_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(wl.errored);
     ASSERT_TRUE(wl.matched);
@@ -35,7 +36,8 @@
 // Test an empty while loop with parentheses.
 TEST_F(WhileStmtTest, EmptyWithParentheses) {
     auto p = parser("while (true) { }");
-    auto wl = p->while_statement();
+    ParserImpl::AttributeList attrs;
+    auto wl = p->while_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(wl.errored);
     ASSERT_TRUE(wl.matched);
@@ -46,7 +48,8 @@
 // Test a while loop with non-empty body.
 TEST_F(WhileStmtTest, Body) {
     auto p = parser("while (true) { discard; }");
-    auto wl = p->while_statement();
+    ParserImpl::AttributeList attrs;
+    auto wl = p->while_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(wl.errored);
     ASSERT_TRUE(wl.matched);
@@ -58,7 +61,8 @@
 // Test a while loop with complex condition.
 TEST_F(WhileStmtTest, ComplexCondition) {
     auto p = parser("while (a + 1 - 2) == 3 { }");
-    auto wl = p->while_statement();
+    ParserImpl::AttributeList attrs;
+    auto wl = p->while_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(wl.errored);
     ASSERT_TRUE(wl.matched);
@@ -69,7 +73,8 @@
 // Test a while loop with complex condition, with parentheses.
 TEST_F(WhileStmtTest, ComplexConditionWithParentheses) {
     auto p = parser("while ((a + 1 - 2) == 3) { }");
-    auto wl = p->while_statement();
+    ParserImpl::AttributeList attrs;
+    auto wl = p->while_statement(attrs);
     EXPECT_FALSE(p->has_error()) << p->error();
     EXPECT_FALSE(wl.errored);
     ASSERT_TRUE(wl.matched);
@@ -77,11 +82,26 @@
     EXPECT_TRUE(wl->body->Empty());
 }
 
+// Test a while loop with attributes.
+TEST_F(WhileStmtTest, WithAttributes) {
+    auto p = parser("@diagnostic(off, derivative_uniformity) while true { }");
+    auto attrs = p->attribute_list();
+    auto wl = p->while_statement(attrs.value);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    EXPECT_FALSE(wl.errored);
+    ASSERT_TRUE(wl.matched);
+
+    EXPECT_TRUE(attrs->IsEmpty());
+    ASSERT_EQ(wl->attributes.Length(), 1u);
+    EXPECT_TRUE(wl->attributes[0]->Is<ast::DiagnosticAttribute>());
+}
+
 class WhileStmtErrorTest : public ParserImplTest {
   public:
     void TestWhileWithError(std::string for_str, std::string error_str) {
         auto p_for = parser(for_str);
-        auto e_for = p_for->while_statement();
+        ParserImpl::AttributeList attrs;
+        auto e_for = p_for->while_statement(attrs);
 
         EXPECT_FALSE(e_for.matched);
         EXPECT_TRUE(e_for.errored);
diff --git a/src/tint/reader/wgsl/token.h b/src/tint/reader/wgsl/token.h
index 43f6831..19bdd40 100644
--- a/src/tint/reader/wgsl/token.h
+++ b/src/tint/reader/wgsl/token.h
@@ -165,17 +165,19 @@
         kContinue,
         /// A 'continuing'
         kContinuing,
+        /// A 'default'
+        kDefault,
         /// A 'diagnostic'
         kDiagnostic,
         /// A 'discard'
         kDiscard,
-        /// A 'default'
-        kDefault,
         /// A 'else'
         kElse,
         /// A 'enable'
         kEnable,
         /// A 'fallthrough'
+        // Note, this isn't a keyword, but a reserved word. We match it as a keyword in order to
+        // provide better diagnostics in case a `fallthrough` is added to a case body.
         kFallthrough,
         /// A 'false'
         kFalse,
@@ -191,10 +193,10 @@
         kLoop,
         /// A 'override'
         kOverride,
-        /// A 'return'
-        kReturn,
         /// A 'requires'
         kRequires,
+        /// A 'return'
+        kReturn,
         /// A 'struct'
         kStruct,
         /// A 'switch'
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 5c09c2a..23f82a2 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -1045,6 +1045,138 @@
 12:34 note: first attribute declared here)");
 }
 
+using SwitchStatementAttributeTest = TestWithParams;
+TEST_P(SwitchStatementAttributeTest, IsValid) {
+    auto& params = GetParam();
+
+    WrapInFunction(Switch(Expr(0_a), utils::Vector{DefaultCase()},
+                          createAttributes(Source{{12, 34}}, *this, params.kind)));
+
+    if (params.should_pass) {
+        EXPECT_TRUE(r()->Resolve()) << r()->error();
+    } else {
+        EXPECT_FALSE(r()->Resolve());
+        EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for switch statements");
+    }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
+                         SwitchStatementAttributeTest,
+                         testing::Values(TestParams{AttributeKind::kAlign, false},
+                                         TestParams{AttributeKind::kBinding, false},
+                                         TestParams{AttributeKind::kBuiltin, false},
+                                         TestParams{AttributeKind::kDiagnostic, true},
+                                         TestParams{AttributeKind::kGroup, false},
+                                         TestParams{AttributeKind::kId, false},
+                                         TestParams{AttributeKind::kInterpolate, false},
+                                         TestParams{AttributeKind::kInvariant, false},
+                                         TestParams{AttributeKind::kLocation, false},
+                                         TestParams{AttributeKind::kMustUse, false},
+                                         TestParams{AttributeKind::kOffset, false},
+                                         TestParams{AttributeKind::kSize, false},
+                                         TestParams{AttributeKind::kStage, false},
+                                         TestParams{AttributeKind::kStride, false},
+                                         TestParams{AttributeKind::kWorkgroup, false},
+                                         TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using IfStatementAttributeTest = TestWithParams;
+TEST_P(IfStatementAttributeTest, IsValid) {
+    auto& params = GetParam();
+
+    WrapInFunction(If(Expr(true), Block(), ElseStmt(),
+                      createAttributes(Source{{12, 34}}, *this, params.kind)));
+
+    if (params.should_pass) {
+        EXPECT_TRUE(r()->Resolve()) << r()->error();
+    } else {
+        EXPECT_FALSE(r()->Resolve());
+        EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for if statements");
+    }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
+                         IfStatementAttributeTest,
+                         testing::Values(TestParams{AttributeKind::kAlign, false},
+                                         TestParams{AttributeKind::kBinding, false},
+                                         TestParams{AttributeKind::kBuiltin, false},
+                                         TestParams{AttributeKind::kDiagnostic, true},
+                                         TestParams{AttributeKind::kGroup, false},
+                                         TestParams{AttributeKind::kId, false},
+                                         TestParams{AttributeKind::kInterpolate, false},
+                                         TestParams{AttributeKind::kInvariant, false},
+                                         TestParams{AttributeKind::kLocation, false},
+                                         TestParams{AttributeKind::kMustUse, false},
+                                         TestParams{AttributeKind::kOffset, false},
+                                         TestParams{AttributeKind::kSize, false},
+                                         TestParams{AttributeKind::kStage, false},
+                                         TestParams{AttributeKind::kStride, false},
+                                         TestParams{AttributeKind::kWorkgroup, false},
+                                         TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using ForStatementAttributeTest = TestWithParams;
+TEST_P(ForStatementAttributeTest, IsValid) {
+    auto& params = GetParam();
+
+    WrapInFunction(For(nullptr, Expr(false), nullptr, Block(),
+                       createAttributes(Source{{12, 34}}, *this, params.kind)));
+
+    if (params.should_pass) {
+        EXPECT_TRUE(r()->Resolve()) << r()->error();
+    } else {
+        EXPECT_FALSE(r()->Resolve());
+        EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for for statements");
+    }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
+                         ForStatementAttributeTest,
+                         testing::Values(TestParams{AttributeKind::kAlign, false},
+                                         TestParams{AttributeKind::kBinding, false},
+                                         TestParams{AttributeKind::kBuiltin, false},
+                                         TestParams{AttributeKind::kDiagnostic, true},
+                                         TestParams{AttributeKind::kGroup, false},
+                                         TestParams{AttributeKind::kId, false},
+                                         TestParams{AttributeKind::kInterpolate, false},
+                                         TestParams{AttributeKind::kInvariant, false},
+                                         TestParams{AttributeKind::kLocation, false},
+                                         TestParams{AttributeKind::kMustUse, false},
+                                         TestParams{AttributeKind::kOffset, false},
+                                         TestParams{AttributeKind::kSize, false},
+                                         TestParams{AttributeKind::kStage, false},
+                                         TestParams{AttributeKind::kStride, false},
+                                         TestParams{AttributeKind::kWorkgroup, false},
+                                         TestParams{AttributeKind::kBindingAndGroup, false}));
+
+using WhileStatementAttributeTest = TestWithParams;
+TEST_P(WhileStatementAttributeTest, IsValid) {
+    auto& params = GetParam();
+
+    WrapInFunction(
+        While(Expr(false), Block(), createAttributes(Source{{12, 34}}, *this, params.kind)));
+
+    if (params.should_pass) {
+        EXPECT_TRUE(r()->Resolve()) << r()->error();
+    } else {
+        EXPECT_FALSE(r()->Resolve());
+        EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for while statements");
+    }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
+                         WhileStatementAttributeTest,
+                         testing::Values(TestParams{AttributeKind::kAlign, false},
+                                         TestParams{AttributeKind::kBinding, false},
+                                         TestParams{AttributeKind::kBuiltin, false},
+                                         TestParams{AttributeKind::kDiagnostic, true},
+                                         TestParams{AttributeKind::kGroup, false},
+                                         TestParams{AttributeKind::kId, false},
+                                         TestParams{AttributeKind::kInterpolate, false},
+                                         TestParams{AttributeKind::kInvariant, false},
+                                         TestParams{AttributeKind::kLocation, false},
+                                         TestParams{AttributeKind::kMustUse, false},
+                                         TestParams{AttributeKind::kOffset, false},
+                                         TestParams{AttributeKind::kSize, false},
+                                         TestParams{AttributeKind::kStage, false},
+                                         TestParams{AttributeKind::kStride, false},
+                                         TestParams{AttributeKind::kWorkgroup, false},
+                                         TestParams{AttributeKind::kBindingAndGroup, false}));
+
 namespace BlockStatementTests {
 class BlockStatementTest : public TestWithParams {
   protected:
diff --git a/src/tint/resolver/builtin_test.cc b/src/tint/resolver/builtin_test.cc
index 2bc024f..80df98f 100644
--- a/src/tint/resolver/builtin_test.cc
+++ b/src/tint/resolver/builtin_test.cc
@@ -50,13 +50,11 @@
 
 using ExpressionList = utils::Vector<const ast::Expression*, 8>;
 
-using BuiltinType = sem::BuiltinType;
-
 using ResolverBuiltinTest = ResolverTest;
 
 struct BuiltinData {
     const char* name;
-    BuiltinType builtin;
+    builtin::Function builtin;
 };
 
 inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
@@ -253,7 +251,7 @@
 struct BuiltinDataWithParamNum {
     uint32_t args_number;
     const char* name;
-    BuiltinType builtin;
+    builtin::Function builtin;
 };
 
 inline std::ostream& operator<<(std::ostream& out, BuiltinDataWithParamNum data) {
@@ -680,55 +678,55 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverTest,
     ResolverBuiltinTest_FloatBuiltin_IdenticalType,
-    testing::Values(BuiltinDataWithParamNum{1, "abs", BuiltinType::kAbs},
-                    BuiltinDataWithParamNum{1, "acos", BuiltinType::kAcos},
-                    BuiltinDataWithParamNum{1, "acosh", BuiltinType::kAcos},
-                    BuiltinDataWithParamNum{1, "asin", BuiltinType::kAsin},
-                    BuiltinDataWithParamNum{1, "asinh", BuiltinType::kAsin},
-                    BuiltinDataWithParamNum{1, "atan", BuiltinType::kAtan},
-                    BuiltinDataWithParamNum{1, "atanh", BuiltinType::kAtan},
-                    BuiltinDataWithParamNum{2, "atan2", BuiltinType::kAtan2},
-                    BuiltinDataWithParamNum{1, "ceil", BuiltinType::kCeil},
-                    BuiltinDataWithParamNum{3, "clamp", BuiltinType::kClamp},
-                    BuiltinDataWithParamNum{1, "cos", BuiltinType::kCos},
-                    BuiltinDataWithParamNum{1, "cosh", BuiltinType::kCosh},
+    testing::Values(BuiltinDataWithParamNum{1, "abs", builtin::Function::kAbs},
+                    BuiltinDataWithParamNum{1, "acos", builtin::Function::kAcos},
+                    BuiltinDataWithParamNum{1, "acosh", builtin::Function::kAcos},
+                    BuiltinDataWithParamNum{1, "asin", builtin::Function::kAsin},
+                    BuiltinDataWithParamNum{1, "asinh", builtin::Function::kAsin},
+                    BuiltinDataWithParamNum{1, "atan", builtin::Function::kAtan},
+                    BuiltinDataWithParamNum{1, "atanh", builtin::Function::kAtan},
+                    BuiltinDataWithParamNum{2, "atan2", builtin::Function::kAtan2},
+                    BuiltinDataWithParamNum{1, "ceil", builtin::Function::kCeil},
+                    BuiltinDataWithParamNum{3, "clamp", builtin::Function::kClamp},
+                    BuiltinDataWithParamNum{1, "cos", builtin::Function::kCos},
+                    BuiltinDataWithParamNum{1, "cosh", builtin::Function::kCosh},
                     // cross: (vec3<T>, vec3<T>) -> vec3<T>
-                    BuiltinDataWithParamNum{1, "degrees", BuiltinType::kDegrees},
+                    BuiltinDataWithParamNum{1, "degrees", builtin::Function::kDegrees},
                     // distance: (T, T) -> T, (vecN<T>, vecN<T>) -> T
-                    BuiltinDataWithParamNum{1, "exp", BuiltinType::kExp},
-                    BuiltinDataWithParamNum{1, "exp2", BuiltinType::kExp2},
+                    BuiltinDataWithParamNum{1, "exp", builtin::Function::kExp},
+                    BuiltinDataWithParamNum{1, "exp2", builtin::Function::kExp2},
                     // faceForward: (vecN<T>, vecN<T>, vecN<T>) -> vecN<T>
-                    BuiltinDataWithParamNum{1, "floor", BuiltinType::kFloor},
-                    BuiltinDataWithParamNum{3, "fma", BuiltinType::kFma},
-                    BuiltinDataWithParamNum{1, "fract", BuiltinType::kFract},
+                    BuiltinDataWithParamNum{1, "floor", builtin::Function::kFloor},
+                    BuiltinDataWithParamNum{3, "fma", builtin::Function::kFma},
+                    BuiltinDataWithParamNum{1, "fract", builtin::Function::kFract},
                     // frexp
-                    BuiltinDataWithParamNum{1, "inverseSqrt", BuiltinType::kInverseSqrt},
+                    BuiltinDataWithParamNum{1, "inverseSqrt", builtin::Function::kInverseSqrt},
                     // ldexp: (T, i32) -> T, (vecN<T>, vecN<i32>) -> vecN<T>
                     // length: (vecN<T>) -> T
-                    BuiltinDataWithParamNum{1, "log", BuiltinType::kLog},
-                    BuiltinDataWithParamNum{1, "log2", BuiltinType::kLog2},
-                    BuiltinDataWithParamNum{2, "max", BuiltinType::kMax},
-                    BuiltinDataWithParamNum{2, "min", BuiltinType::kMin},
+                    BuiltinDataWithParamNum{1, "log", builtin::Function::kLog},
+                    BuiltinDataWithParamNum{1, "log2", builtin::Function::kLog2},
+                    BuiltinDataWithParamNum{2, "max", builtin::Function::kMax},
+                    BuiltinDataWithParamNum{2, "min", builtin::Function::kMin},
                     // Note that `mix(vecN<f32>, vecN<f32>, f32) -> vecN<f32>` is not tested here.
-                    BuiltinDataWithParamNum{3, "mix", BuiltinType::kMix},
+                    BuiltinDataWithParamNum{3, "mix", builtin::Function::kMix},
                     // modf
                     // normalize: (vecN<T>) -> vecN<T>
-                    BuiltinDataWithParamNum{2, "pow", BuiltinType::kPow},
+                    BuiltinDataWithParamNum{2, "pow", builtin::Function::kPow},
                     // quantizeToF16 is not implemented yet.
-                    BuiltinDataWithParamNum{1, "radians", BuiltinType::kRadians},
+                    BuiltinDataWithParamNum{1, "radians", builtin::Function::kRadians},
                     // reflect: (vecN<T>, vecN<T>) -> vecN<T>
                     // refract: (vecN<T>, vecN<T>, T) -> vecN<T>
-                    BuiltinDataWithParamNum{1, "round", BuiltinType::kRound},
+                    BuiltinDataWithParamNum{1, "round", builtin::Function::kRound},
                     // saturate not implemented yet.
-                    BuiltinDataWithParamNum{1, "sign", BuiltinType::kSign},
-                    BuiltinDataWithParamNum{1, "sin", BuiltinType::kSin},
-                    BuiltinDataWithParamNum{1, "sinh", BuiltinType::kSinh},
-                    BuiltinDataWithParamNum{3, "smoothstep", BuiltinType::kSmoothstep},
-                    BuiltinDataWithParamNum{1, "sqrt", BuiltinType::kSqrt},
-                    BuiltinDataWithParamNum{2, "step", BuiltinType::kStep},
-                    BuiltinDataWithParamNum{1, "tan", BuiltinType::kTan},
-                    BuiltinDataWithParamNum{1, "tanh", BuiltinType::kTanh},
-                    BuiltinDataWithParamNum{1, "trunc", BuiltinType::kTrunc}));
+                    BuiltinDataWithParamNum{1, "sign", builtin::Function::kSign},
+                    BuiltinDataWithParamNum{1, "sin", builtin::Function::kSin},
+                    BuiltinDataWithParamNum{1, "sinh", builtin::Function::kSinh},
+                    BuiltinDataWithParamNum{3, "smoothstep", builtin::Function::kSmoothstep},
+                    BuiltinDataWithParamNum{1, "sqrt", builtin::Function::kSqrt},
+                    BuiltinDataWithParamNum{2, "step", builtin::Function::kStep},
+                    BuiltinDataWithParamNum{1, "tan", builtin::Function::kTan},
+                    BuiltinDataWithParamNum{1, "tanh", builtin::Function::kTanh},
+                    BuiltinDataWithParamNum{1, "trunc", builtin::Function::kTrunc}));
 
 using ResolverBuiltinFloatTest = ResolverTest;
 
@@ -1409,7 +1407,7 @@
 struct BuiltinDataWithParamNum {
     uint32_t args_number;
     const char* name;
-    BuiltinType builtin;
+    builtin::Function builtin;
 };
 
 inline std::ostream& operator<<(std::ostream& out, BuiltinDataWithParamNum data) {
@@ -1805,18 +1803,18 @@
     ResolverTest,
     ResolverBuiltinTest_IntegerBuiltin_IdenticalType,
     testing::Values(
-        BuiltinDataWithParamNum{1, "abs", BuiltinType::kAbs},
-        BuiltinDataWithParamNum{3, "clamp", BuiltinType::kClamp},
-        BuiltinDataWithParamNum{1, "countLeadingZeros", BuiltinType::kCountLeadingZeros},
-        BuiltinDataWithParamNum{1, "countOneBits", BuiltinType::kCountOneBits},
-        BuiltinDataWithParamNum{1, "countTrailingZeros", BuiltinType::kCountTrailingZeros},
+        BuiltinDataWithParamNum{1, "abs", builtin::Function::kAbs},
+        BuiltinDataWithParamNum{3, "clamp", builtin::Function::kClamp},
+        BuiltinDataWithParamNum{1, "countLeadingZeros", builtin::Function::kCountLeadingZeros},
+        BuiltinDataWithParamNum{1, "countOneBits", builtin::Function::kCountOneBits},
+        BuiltinDataWithParamNum{1, "countTrailingZeros", builtin::Function::kCountTrailingZeros},
         // extractBits: (T, u32, u32) -> T
-        BuiltinDataWithParamNum{1, "firstLeadingBit", BuiltinType::kFirstLeadingBit},
-        BuiltinDataWithParamNum{1, "firstTrailingBit", BuiltinType::kFirstTrailingBit},
+        BuiltinDataWithParamNum{1, "firstLeadingBit", builtin::Function::kFirstLeadingBit},
+        BuiltinDataWithParamNum{1, "firstTrailingBit", builtin::Function::kFirstTrailingBit},
         // insertBits: (T, T, u32, u32) -> T
-        BuiltinDataWithParamNum{2, "max", BuiltinType::kMax},
-        BuiltinDataWithParamNum{2, "min", BuiltinType::kMin},
-        BuiltinDataWithParamNum{1, "reverseBits", BuiltinType::kReverseBits}));
+        BuiltinDataWithParamNum{2, "max", builtin::Function::kMax},
+        BuiltinDataWithParamNum{2, "min", builtin::Function::kMin},
+        BuiltinDataWithParamNum{1, "reverseBits", builtin::Function::kReverseBits}));
 
 }  // namespace integer_builtin_tests
 
@@ -2263,7 +2261,9 @@
         case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
             return R"(textureGatherCompare(texture, sampler, coords, array_index, depth_ref))";
         case ValidTextureOverload::kNumLayers2dArray:
+        case ValidTextureOverload::kNumLayersCubeArray:
         case ValidTextureOverload::kNumLayersDepth2dArray:
+        case ValidTextureOverload::kNumLayersDepthCubeArray:
         case ValidTextureOverload::kNumLayersStorageWO2dArray:
             return R"(textureNumLayers(texture))";
         case ValidTextureOverload::kNumLevels2d:
@@ -2553,8 +2553,8 @@
 TEST_P(ResolverBuiltinTest_DataPacking, InferType) {
     auto param = GetParam();
 
-    bool pack4 =
-        param.builtin == BuiltinType::kPack4X8Snorm || param.builtin == BuiltinType::kPack4X8Unorm;
+    bool pack4 = param.builtin == builtin::Function::kPack4X8Snorm ||
+                 param.builtin == builtin::Function::kPack4X8Unorm;
 
     auto* call = pack4 ? Call(param.name, vec4<f32>(1_f, 2_f, 3_f, 4_f))
                        : Call(param.name, vec2<f32>(1_f, 2_f));
@@ -2568,8 +2568,8 @@
 TEST_P(ResolverBuiltinTest_DataPacking, Error_IncorrectParamType) {
     auto param = GetParam();
 
-    bool pack4 =
-        param.builtin == BuiltinType::kPack4X8Snorm || param.builtin == BuiltinType::kPack4X8Unorm;
+    bool pack4 = param.builtin == builtin::Function::kPack4X8Snorm ||
+                 param.builtin == builtin::Function::kPack4X8Unorm;
 
     auto* call = pack4 ? Call(param.name, vec4<i32>(1_i, 2_i, 3_i, 4_i))
                        : Call(param.name, vec2<i32>(1_i, 2_i));
@@ -2594,8 +2594,8 @@
 TEST_P(ResolverBuiltinTest_DataPacking, Error_TooManyParams) {
     auto param = GetParam();
 
-    bool pack4 =
-        param.builtin == BuiltinType::kPack4X8Snorm || param.builtin == BuiltinType::kPack4X8Unorm;
+    bool pack4 = param.builtin == builtin::Function::kPack4X8Snorm ||
+                 param.builtin == builtin::Function::kPack4X8Unorm;
 
     auto* call = pack4 ? Call(param.name, vec4<f32>(1_f, 2_f, 3_f, 4_f), 1_f)
                        : Call(param.name, vec2<f32>(1_f, 2_f), 1_f);
@@ -2606,14 +2606,14 @@
     EXPECT_THAT(r()->error(), HasSubstr("error: no matching call to " + std::string(param.name)));
 }
 
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         ResolverBuiltinTest_DataPacking,
-                         testing::Values(BuiltinData{"pack4x8snorm", BuiltinType::kPack4X8Snorm},
-                                         BuiltinData{"pack4x8unorm", BuiltinType::kPack4X8Unorm},
-                                         BuiltinData{"pack2x16snorm", BuiltinType::kPack2X16Snorm},
-                                         BuiltinData{"pack2x16unorm", BuiltinType::kPack2X16Unorm},
-                                         BuiltinData{"pack2x16float",
-                                                     BuiltinType::kPack2X16Float}));
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverBuiltinTest_DataPacking,
+    testing::Values(BuiltinData{"pack4x8snorm", builtin::Function::kPack4X8Snorm},
+                    BuiltinData{"pack4x8unorm", builtin::Function::kPack4X8Unorm},
+                    BuiltinData{"pack2x16snorm", builtin::Function::kPack2X16Snorm},
+                    BuiltinData{"pack2x16unorm", builtin::Function::kPack2X16Unorm},
+                    BuiltinData{"pack2x16float", builtin::Function::kPack2X16Float}));
 
 }  // namespace data_packing_builtin_tests
 
@@ -2624,8 +2624,8 @@
 TEST_P(ResolverBuiltinTest_DataUnpacking, InferType) {
     auto param = GetParam();
 
-    bool pack4 = param.builtin == BuiltinType::kUnpack4X8Snorm ||
-                 param.builtin == BuiltinType::kUnpack4X8Unorm;
+    bool pack4 = param.builtin == builtin::Function::kUnpack4X8Snorm ||
+                 param.builtin == builtin::Function::kUnpack4X8Unorm;
 
     auto* call = Call(param.name, 1_u);
     WrapInFunction(call);
@@ -2643,11 +2643,11 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverTest,
     ResolverBuiltinTest_DataUnpacking,
-    testing::Values(BuiltinData{"unpack4x8snorm", BuiltinType::kUnpack4X8Snorm},
-                    BuiltinData{"unpack4x8unorm", BuiltinType::kUnpack4X8Unorm},
-                    BuiltinData{"unpack2x16snorm", BuiltinType::kUnpack2X16Snorm},
-                    BuiltinData{"unpack2x16unorm", BuiltinType::kUnpack2X16Unorm},
-                    BuiltinData{"unpack2x16float", BuiltinType::kUnpack2X16Float}));
+    testing::Values(BuiltinData{"unpack4x8snorm", builtin::Function::kUnpack4X8Snorm},
+                    BuiltinData{"unpack4x8unorm", builtin::Function::kUnpack4X8Unorm},
+                    BuiltinData{"unpack2x16snorm", builtin::Function::kUnpack2X16Snorm},
+                    BuiltinData{"unpack2x16unorm", builtin::Function::kUnpack2X16Unorm},
+                    BuiltinData{"unpack2x16float", builtin::Function::kUnpack2X16Float}));
 
 }  // namespace data_unpacking_builtin_tests
 
@@ -2680,8 +2680,8 @@
 INSTANTIATE_TEST_SUITE_P(
     ResolverTest,
     ResolverBuiltinTest_Barrier,
-    testing::Values(BuiltinData{"storageBarrier", BuiltinType::kStorageBarrier},
-                    BuiltinData{"workgroupBarrier", BuiltinType::kWorkgroupBarrier}));
+    testing::Values(BuiltinData{"storageBarrier", builtin::Function::kStorageBarrier},
+                    BuiltinData{"workgroupBarrier", builtin::Function::kWorkgroupBarrier}));
 
 }  // namespace synchronization_builtin_tests
 
diff --git a/src/tint/resolver/call_validation_test.cc b/src/tint/resolver/call_validation_test.cc
index b749df9..b00db68 100644
--- a/src/tint/resolver/call_validation_test.cc
+++ b/src/tint/resolver/call_validation_test.cc
@@ -478,5 +478,34 @@
     EXPECT_EQ(r()->error(), "12:34 error: ignoring return value of builtin 'max'");
 }
 
+TEST_F(ResolverCallValidationTest, UnexpectedFunctionTemplateArgs) {
+    // fn a() {}
+    // fn b() {
+    //   a<i32>();
+    // }
+    Func(Source{{56, 78}}, "a", utils::Empty, ty.void_(), utils::Empty);
+    Func("b", utils::Empty, ty.void_(),
+         utils::Vector{
+             CallStmt(Call(Ident(Source{{12, 34}}, "a", "i32"))),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: function 'a' does not take template arguments
+56:78 note: function 'a' declared here)");
+}
+
+TEST_F(ResolverCallValidationTest, UnexpectedBuiltinTemplateArgs) {
+    // fn f() {
+    //   min<i32>(1, 2);
+    // }
+    Func("f", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Var("v", Call(Ident(Source{{12, 34}}, "min", "i32"), 1_a, 2_a))),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), R"(12:34 error: builtin 'min' does not take template arguments)");
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index 325ccb0..b90ea00 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -30,6 +30,7 @@
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/member_accessor_expression.h"
 #include "src/tint/sem/value_constructor.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/abstract_float.h"
 #include "src/tint/type/abstract_int.h"
 #include "src/tint/type/array.h"
@@ -325,35 +326,20 @@
                                   const Source& source,
                                   bool use_runtime_semantics);
 
-ConstEval::Result SplatConvert(const constant::Splat* splat,
-                               ProgramBuilder& builder,
-                               const type::Type* target_ty,
-                               const Source& source,
-                               bool use_runtime_semantics) {
-    // Convert the single splatted element type.
-    auto conv_el = ConvertInternal(splat->el, builder, type::Type::ElementOf(target_ty), source,
-                                   use_runtime_semantics);
-    if (!conv_el) {
-        return utils::Failure;
-    }
-    if (!conv_el.Get()) {
-        return nullptr;
-    }
-    return builder.create<constant::Splat>(target_ty, conv_el.Get(), splat->count);
-}
-
-ConstEval::Result CompositeConvert(const constant::Composite* composite,
+ConstEval::Result CompositeConvert(const constant::Value* value,
                                    ProgramBuilder& builder,
                                    const type::Type* target_ty,
                                    const Source& source,
                                    bool use_runtime_semantics) {
+    const size_t el_count = value->NumElements();
+
     // Convert each of the composite element types.
     utils::Vector<const constant::Value*, 4> conv_els;
-    conv_els.Reserve(composite->elements.Length());
+    conv_els.Reserve(el_count);
 
     std::function<const type::Type*(size_t idx)> target_el_ty;
     if (auto* str = target_ty->As<type::Struct>()) {
-        if (TINT_UNLIKELY(str->Members().Length() != composite->elements.Length())) {
+        if (TINT_UNLIKELY(str->Members().Length() != el_count)) {
             TINT_ICE(Resolver, builder.Diagnostics())
                 << "const-eval conversion of structure has mismatched element counts";
             return utils::Failure;
@@ -364,7 +350,8 @@
         target_el_ty = [el_ty](size_t) { return el_ty; };
     }
 
-    for (auto* el : composite->elements) {
+    for (size_t i = 0; i < el_count; i++) {
+        auto* el = value->Index(i);
         auto conv_el = ConvertInternal(el, builder, target_el_ty(conv_els.Length()), source,
                                        use_runtime_semantics);
         if (!conv_el) {
@@ -378,6 +365,40 @@
     return builder.create<constant::Composite>(target_ty, std::move(conv_els));
 }
 
+ConstEval::Result SplatConvert(const constant::Splat* splat,
+                               ProgramBuilder& builder,
+                               const type::Type* target_ty,
+                               const Source& source,
+                               bool use_runtime_semantics) {
+    const type::Type* target_el_ty = nullptr;
+    if (auto* str = target_ty->As<type::Struct>()) {
+        // Structure conversion.
+        auto members = str->Members();
+        target_el_ty = members[0]->Type();
+
+        // Structures can only be converted during materialization. The user cannot declare the
+        // target structure type, so each member type must be the same default materialization type.
+        for (size_t i = 1; i < members.Length(); i++) {
+            if (members[i]->Type() != target_el_ty) {
+                TINT_ICE(Resolver, builder.Diagnostics())
+                    << "inconsistent target struct member types for SplatConvert";
+                return utils::Failure;
+            }
+        }
+    } else {
+        target_el_ty = type::Type::ElementOf(target_ty);
+    }
+    // Convert the single splatted element type.
+    auto conv_el = ConvertInternal(splat->el, builder, target_el_ty, source, use_runtime_semantics);
+    if (!conv_el) {
+        return utils::Failure;
+    }
+    if (!conv_el.Get()) {
+        return nullptr;
+    }
+    return builder.create<constant::Splat>(target_ty, conv_el.Get(), splat->count);
+}
+
 ConstEval::Result ConvertInternal(const constant::Value* c,
                                   ProgramBuilder& builder,
                                   const type::Type* target_ty,
diff --git a/src/tint/resolver/const_eval_builtin_test.cc b/src/tint/resolver/const_eval_builtin_test.cc
index bd32779..2cef1d1 100644
--- a/src/tint/resolver/const_eval_builtin_test.cc
+++ b/src/tint/resolver/const_eval_builtin_test.cc
@@ -139,7 +139,7 @@
     return Case{std::move(args), std::move(err)};
 }
 
-using ResolverConstEvalBuiltinTest = ResolverTestWithParam<std::tuple<sem::BuiltinType, Case>>;
+using ResolverConstEvalBuiltinTest = ResolverTestWithParam<std::tuple<builtin::Function, Case>>;
 
 TEST_P(ResolverConstEvalBuiltinTest, Test) {
     Enable(builtin::Extension::kF16);
@@ -152,7 +152,7 @@
         args.Push(a.Expr(*this));
     }
 
-    auto* expr = Call(Source{{12, 34}}, sem::str(builtin), std::move(args));
+    auto* expr = Call(Source{{12, 34}}, builtin::str(builtin), std::move(args));
     GlobalConst("C", expr);
 
     if (c.expected) {
@@ -187,7 +187,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     MixedAbstractArgs,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAtan2),
+    testing::Combine(testing::Values(builtin::Function::kAtan2),
                      testing::ValuesIn(std::vector{
                          C({0_a, -0.0_a}, kPi<AFloat>),
                          C({1.0_a, 0_a}, kPiOver2<AFloat>),
@@ -222,7 +222,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Abs,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAbs),
+    testing::Combine(testing::Values(builtin::Function::kAbs),
                      testing::ValuesIn(Concat(AbsCases<AInt>(),  //
                                               AbsCases<i32>(),
                                               AbsCases<u32>(),
@@ -257,7 +257,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     All,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAll), testing::ValuesIn(AllCases())));
+    testing::Combine(testing::Values(builtin::Function::kAll), testing::ValuesIn(AllCases())));
 
 static std::vector<Case> AnyCases() {
     return {
@@ -286,7 +286,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Any,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAny), testing::ValuesIn(AnyCases())));
+    testing::Combine(testing::Values(builtin::Function::kAny), testing::ValuesIn(AnyCases())));
 
 template <typename T>
 std::vector<Case> Atan2Cases() {
@@ -315,7 +315,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Atan2,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAtan2),
+    testing::Combine(testing::Values(builtin::Function::kAtan2),
                      testing::ValuesIn(Concat(Atan2Cases<AFloat>(),  //
                                               Atan2Cases<f32>(),
                                               Atan2Cases<f16>()))));
@@ -336,7 +336,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Atan,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAtan),
+    testing::Combine(testing::Values(builtin::Function::kAtan),
                      testing::ValuesIn(Concat(AtanCases<AFloat>(),  //
                                               AtanCases<f32>(),
                                               AtanCases<f16>()))));
@@ -361,7 +361,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Atanh,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAtanh),
+    testing::Combine(testing::Values(builtin::Function::kAtanh),
                      testing::ValuesIn(Concat(AtanhCases<AFloat>(),  //
                                               AtanhCases<f32>(),
                                               AtanhCases<f16>()))));
@@ -387,7 +387,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Acos,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAcos),
+    testing::Combine(testing::Values(builtin::Function::kAcos),
                      testing::ValuesIn(Concat(AcosCases<AFloat>(),  //
                                               AcosCases<f32>(),
                                               AcosCases<f16>()))));
@@ -409,7 +409,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Acosh,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAcosh),
+    testing::Combine(testing::Values(builtin::Function::kAcosh),
                      testing::ValuesIn(Concat(AcoshCases<AFloat>(),  //
                                               AcoshCases<f32>(),
                                               AcoshCases<f16>()))));
@@ -436,7 +436,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Asin,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAsin),
+    testing::Combine(testing::Values(builtin::Function::kAsin),
                      testing::ValuesIn(Concat(AsinCases<AFloat>(),  //
                                               AsinCases<f32>(),
                                               AsinCases<f16>()))));
@@ -460,7 +460,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Asinh,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kAsinh),
+    testing::Combine(testing::Values(builtin::Function::kAsinh),
                      testing::ValuesIn(Concat(AsinhCases<AFloat>(),  //
                                               AsinhCases<f32>(),
                                               AsinhCases<f16>()))));
@@ -482,7 +482,7 @@
     Ceil,
     ResolverConstEvalBuiltinTest,
     testing::Combine(
-        testing::Values(sem::BuiltinType::kCeil),
+        testing::Values(builtin::Function::kCeil),
         testing::ValuesIn(Concat(CeilCases<AFloat>(), CeilCases<f32>(), CeilCases<f16>()))));
 
 template <typename T>
@@ -512,7 +512,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Clamp,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kClamp),
+    testing::Combine(testing::Values(builtin::Function::kClamp),
                      testing::ValuesIn(Concat(ClampCases<AInt>(),  //
                                               ClampCases<i32>(),
                                               ClampCases<u32>(),
@@ -535,7 +535,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Cos,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kCos),
+    testing::Combine(testing::Values(builtin::Function::kCos),
                      testing::ValuesIn(Concat(CosCases<AFloat>(),  //
                                               CosCases<f32>(),
                                               CosCases<f16>()))));
@@ -561,7 +561,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Cosh,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kCosh),
+    testing::Combine(testing::Values(builtin::Function::kCosh),
                      testing::ValuesIn(Concat(CoshCases<AFloat>(),  //
                                               CoshCases<f32>(),
                                               CoshCases<f16>()))));
@@ -608,7 +608,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     CountLeadingZeros,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kCountLeadingZeros),
+    testing::Combine(testing::Values(builtin::Function::kCountLeadingZeros),
                      testing::ValuesIn(Concat(CountLeadingZerosCases<i32>(),  //
                                               CountLeadingZerosCases<u32>()))));
 
@@ -654,7 +654,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     CountTrailingZeros,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kCountTrailingZeros),
+    testing::Combine(testing::Values(builtin::Function::kCountTrailingZeros),
                      testing::ValuesIn(Concat(CountTrailingZerosCases<i32>(),  //
                                               CountTrailingZerosCases<u32>()))));
 
@@ -691,7 +691,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     CountOneBits,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kCountOneBits),
+    testing::Combine(testing::Values(builtin::Function::kCountOneBits),
                      testing::ValuesIn(Concat(CountOneBitsCases<i32>(),  //
                                               CountOneBitsCases<u32>()))));
 
@@ -790,7 +790,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Cross,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kCross),
+    testing::Combine(testing::Values(builtin::Function::kCross),
                      testing::ValuesIn(Concat(CrossCases<AFloat>(),  //
                                               CrossCases<f32>(),     //
                                               CrossCases<f16>()))));
@@ -817,7 +817,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Distance,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kDistance),
+    testing::Combine(testing::Values(builtin::Function::kDistance),
                      testing::ValuesIn(Concat(DistanceCases<AFloat>(),  //
                                               DistanceCases<f32>(),     //
                                               DistanceCases<f16>()))));
@@ -865,7 +865,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Dot,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kDot),
+    testing::Combine(testing::Values(builtin::Function::kDot),
                      testing::ValuesIn(Concat(DotCases<AInt>(),    //
                                               DotCases<i32>(),     //
                                               DotCases<u32>(),     //
@@ -945,7 +945,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Determinant,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kDeterminant),
+    testing::Combine(testing::Values(builtin::Function::kDeterminant),
                      testing::ValuesIn(Concat(DeterminantCases<AFloat>(),  //
                                               DeterminantCases<f32>(),     //
                                               DeterminantCases<f16>()))));
@@ -1027,7 +1027,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     FaceForward,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kFaceForward),
+    testing::Combine(testing::Values(builtin::Function::kFaceForward),
                      testing::ValuesIn(Concat(FaceForwardCases<AFloat>(),  //
                                               FaceForwardCases<f32>(),     //
                                               FaceForwardCases<f16>()))));
@@ -1090,7 +1090,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     FirstLeadingBit,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kFirstLeadingBit),
+    testing::Combine(testing::Values(builtin::Function::kFirstLeadingBit),
                      testing::ValuesIn(Concat(FirstLeadingBitCases<i32>(),  //
                                               FirstLeadingBitCases<u32>()))));
 
@@ -1123,7 +1123,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     FirstTrailingBit,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kFirstTrailingBit),
+    testing::Combine(testing::Values(builtin::Function::kFirstTrailingBit),
                      testing::ValuesIn(Concat(FirstTrailingBitCases<i32>(),  //
                                               FirstTrailingBitCases<u32>()))));
 
@@ -1143,7 +1143,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Floor,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kFloor),
+    testing::Combine(testing::Values(builtin::Function::kFloor),
                      testing::ValuesIn(Concat(FloorCases<AFloat>(),  //
                                               FloorCases<f32>(),
                                               FloorCases<f16>()))));
@@ -1167,7 +1167,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Fma,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kFma),
+    testing::Combine(testing::Values(builtin::Function::kFma),
                      testing::ValuesIn(Concat(FmaCases<AFloat>(),  //
                                               FmaCases<f32>(),
                                               FmaCases<f16>()))));
@@ -1199,7 +1199,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Fract,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kFract),
+    testing::Combine(testing::Values(builtin::Function::kFract),
                      testing::ValuesIn(Concat(FractCases<AFloat>(),  //
                                               FractCases<f32>(),
                                               FractCases<f16>()))));
@@ -1256,7 +1256,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Frexp,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kFrexp),
+    testing::Combine(testing::Values(builtin::Function::kFrexp),
                      testing::ValuesIn(Concat(FrexpCases<AFloat>(),  //
                                               FrexpCases<f32>(),     //
                                               FrexpCases<f16>()))));
@@ -1338,7 +1338,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     InsertBits,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kInsertBits),
+    testing::Combine(testing::Values(builtin::Function::kInsertBits),
                      testing::ValuesIn(Concat(InsertBitsCases<i32>(),  //
                                               InsertBitsCases<u32>()))));
 
@@ -1358,7 +1358,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     InverseSqrt,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kInverseSqrt),
+    testing::Combine(testing::Values(builtin::Function::kInverseSqrt),
                      testing::ValuesIn(Concat(InverseSqrtCases<AFloat>(),  //
                                               InverseSqrtCases<f32>(),
                                               InverseSqrtCases<f16>()))));
@@ -1377,7 +1377,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     DegreesAFloat,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kDegrees),
+    testing::Combine(testing::Values(builtin::Function::kDegrees),
                      testing::ValuesIn(DegreesAFloatCases<AFloat>())));
 
 template <typename T>
@@ -1394,7 +1394,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     DegreesF32,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kDegrees),
+    testing::Combine(testing::Values(builtin::Function::kDegrees),
                      testing::ValuesIn(DegreesF32Cases<f32>())));
 
 template <typename T>
@@ -1411,7 +1411,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     DegreesF16,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kDegrees),
+    testing::Combine(testing::Values(builtin::Function::kDegrees),
                      testing::ValuesIn(DegreesF16Cases<f16>())));
 
 template <typename T>
@@ -1428,7 +1428,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Exp,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kExp),
+    testing::Combine(testing::Values(builtin::Function::kExp),
                      testing::ValuesIn(Concat(ExpCases<AFloat>(),  //
                                               ExpCases<f32>(),
                                               ExpCases<f16>()))));
@@ -1449,7 +1449,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Exp2,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kExp2),
+    testing::Combine(testing::Values(builtin::Function::kExp2),
                      testing::ValuesIn(Concat(Exp2Cases<AFloat>(),  //
                                               Exp2Cases<f32>(),
                                               Exp2Cases<f16>()))));
@@ -1546,7 +1546,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     ExtractBits,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kExtractBits),
+    testing::Combine(testing::Values(builtin::Function::kExtractBits),
                      testing::ValuesIn(Concat(ExtractBitsCases<i32>(),  //
                                               ExtractBitsCases<u32>()))));
 
@@ -1610,7 +1610,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Ldexp,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kLdexp),
+    testing::Combine(testing::Values(builtin::Function::kLdexp),
                      testing::ValuesIn(Concat(LdexpCases<AFloat>(),  //
                                               LdexpCases<f32>(),
                                               LdexpCases<f16>()))));
@@ -1669,7 +1669,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Length,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kLength),
+    testing::Combine(testing::Values(builtin::Function::kLength),
                      testing::ValuesIn(Concat(LengthCases<AFloat>(),  //
                                               LengthCases<f32>(),
                                               LengthCases<f16>()))));
@@ -1685,7 +1685,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Log,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kLog),
+    testing::Combine(testing::Values(builtin::Function::kLog),
                      testing::ValuesIn(Concat(LogCases<AFloat>(),  //
                                               LogCases<f32>(),
                                               LogCases<f16>()))));
@@ -1698,7 +1698,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     LogF16,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kLog),
+    testing::Combine(testing::Values(builtin::Function::kLog),
                      testing::ValuesIn(LogF16Cases<f16>())));
 template <typename T>
 std::vector<Case> LogF32Cases() {
@@ -1709,7 +1709,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     LogF32,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kLog),
+    testing::Combine(testing::Values(builtin::Function::kLog),
                      testing::ValuesIn(LogF32Cases<f32>())));
 
 template <typename T>
@@ -1721,7 +1721,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     LogAbstract,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kLog),
+    testing::Combine(testing::Values(builtin::Function::kLog),
                      testing::ValuesIn(LogAbstractCases<AFloat>())));
 
 template <typename T>
@@ -1739,7 +1739,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Log2,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kLog2),
+    testing::Combine(testing::Values(builtin::Function::kLog2),
                      testing::ValuesIn(Concat(Log2Cases<AFloat>(),  //
                                               Log2Cases<f32>(),
                                               Log2Cases<f16>()))));
@@ -1752,7 +1752,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Log2F16,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kLog2),
+    testing::Combine(testing::Values(builtin::Function::kLog2),
                      testing::ValuesIn(Log2F16Cases<f16>())));
 template <typename T>
 std::vector<Case> Log2F32Cases() {
@@ -1763,7 +1763,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Log2F32,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kLog2),
+    testing::Combine(testing::Values(builtin::Function::kLog2),
                      testing::ValuesIn(Log2F32Cases<f32>())));
 template <typename T>
 std::vector<Case> Log2AbstractCases() {
@@ -1774,7 +1774,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Log2Abstract,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kLog2),
+    testing::Combine(testing::Values(builtin::Function::kLog2),
                      testing::ValuesIn(Log2AbstractCases<AFloat>())));
 
 template <typename T>
@@ -1797,7 +1797,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Max,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kMax),
+    testing::Combine(testing::Values(builtin::Function::kMax),
                      testing::ValuesIn(Concat(MaxCases<AInt>(),  //
                                               MaxCases<i32>(),
                                               MaxCases<u32>(),
@@ -1823,7 +1823,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Min,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kMin),
+    testing::Combine(testing::Values(builtin::Function::kMin),
                      testing::ValuesIn(Concat(MinCases<AInt>(),  //
                                               MinCases<i32>(),
                                               MinCases<u32>(),
@@ -1913,7 +1913,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Mix,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kMix),
+    testing::Combine(testing::Values(builtin::Function::kMix),
                      testing::ValuesIn(Concat(MixCases<AFloat>(),  //
                                               MixCases<f32>(),     //
                                               MixCases<f16>()))));
@@ -1947,7 +1947,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Modf,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kModf),
+    testing::Combine(testing::Values(builtin::Function::kModf),
                      testing::ValuesIn(Concat(ModfCases<AFloat>(),  //
                                               ModfCases<f32>(),     //
                                               ModfCases<f16>()))));
@@ -1977,7 +1977,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Normalize,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kNormalize),
+    testing::Combine(testing::Values(builtin::Function::kNormalize),
                      testing::ValuesIn(Concat(NormalizeCases<AFloat>(),  //
                                               NormalizeCases<f32>(),     //
                                               NormalizeCases<f16>()))));
@@ -1997,7 +1997,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pack4x8snorm,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kPack4X8Snorm),
+    testing::Combine(testing::Values(builtin::Function::kPack4X8Snorm),
                      testing::ValuesIn(Pack4x8snormCases())));
 
 std::vector<Case> Pack4x8unormCases() {
@@ -2014,7 +2014,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pack4x8unorm,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kPack4X8Unorm),
+    testing::Combine(testing::Values(builtin::Function::kPack4X8Unorm),
                      testing::ValuesIn(Pack4x8unormCases())));
 
 std::vector<Case> Pack2x16floatCases() {
@@ -2035,7 +2035,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pack2x16float,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kPack2X16Float),
+    testing::Combine(testing::Values(builtin::Function::kPack2X16Float),
                      testing::ValuesIn(Pack2x16floatCases())));
 
 std::vector<Case> Pack2x16snormCases() {
@@ -2053,7 +2053,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pack2x16snorm,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kPack2X16Snorm),
+    testing::Combine(testing::Values(builtin::Function::kPack2X16Snorm),
                      testing::ValuesIn(Pack2x16snormCases())));
 
 std::vector<Case> Pack2x16unormCases() {
@@ -2067,7 +2067,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pack2x16unorm,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kPack2X16Unorm),
+    testing::Combine(testing::Values(builtin::Function::kPack2X16Unorm),
                      testing::ValuesIn(Pack2x16unormCases())));
 
 template <typename T>
@@ -2113,7 +2113,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Pow,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kPow),
+    testing::Combine(testing::Values(builtin::Function::kPow),
                      testing::ValuesIn(Concat(PowCases<AFloat>(),  //
                                               PowCases<f32>(),     //
                                               PowCases<f16>()))));
@@ -2160,7 +2160,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     ReverseBits,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kReverseBits),
+    testing::Combine(testing::Values(builtin::Function::kReverseBits),
                      testing::ValuesIn(Concat(ReverseBitsCases<i32>(),  //
                                               ReverseBitsCases<u32>()))));
 
@@ -2212,7 +2212,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Reflect,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kReflect),
+    testing::Combine(testing::Values(builtin::Function::kReflect),
                      testing::ValuesIn(Concat(ReflectCases<AFloat>(),  //
                                               ReflectCases<f32>(),     //
                                               ReflectCases<f16>()))));
@@ -2294,7 +2294,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Refract,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kRefract),
+    testing::Combine(testing::Values(builtin::Function::kRefract),
                      testing::ValuesIn(Concat(RefractCases<AFloat>(),  //
                                               RefractCases<f32>(),     //
                                               RefractCases<f16>()))));
@@ -2313,7 +2313,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Radians,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kRadians),
+    testing::Combine(testing::Values(builtin::Function::kRadians),
                      testing::ValuesIn(Concat(RadiansCases<AFloat>(),  //
                                               RadiansCases<f32>()))));
 
@@ -2331,7 +2331,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     RadiansF16,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kRadians),
+    testing::Combine(testing::Values(builtin::Function::kRadians),
                      testing::ValuesIn(RadiansF16Cases<f16>())));
 
 template <typename T>
@@ -2357,7 +2357,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Round,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kRound),
+    testing::Combine(testing::Values(builtin::Function::kRound),
                      testing::ValuesIn(Concat(RoundCases<AFloat>(),  //
                                               RoundCases<f32>(),
                                               RoundCases<f16>()))));
@@ -2382,7 +2382,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Saturate,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kSaturate),
+    testing::Combine(testing::Values(builtin::Function::kSaturate),
                      testing::ValuesIn(Concat(SaturateCases<AFloat>(),  //
                                               SaturateCases<f32>(),
                                               SaturateCases<f16>()))));
@@ -2424,7 +2424,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Select,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kSelect),
+    testing::Combine(testing::Values(builtin::Function::kSelect),
                      testing::ValuesIn(Concat(SelectCases<AInt>(),  //
                                               SelectCases<i32>(),
                                               SelectCases<u32>(),
@@ -2465,7 +2465,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Sign,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kSign),
+    testing::Combine(testing::Values(builtin::Function::kSign),
                      testing::ValuesIn(Concat(SignCases<AInt>(),  //
                                               SignCases<i32>(),
                                               SignCases<AFloat>(),
@@ -2487,7 +2487,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Sin,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kSin),
+    testing::Combine(testing::Values(builtin::Function::kSin),
                      testing::ValuesIn(Concat(SinCases<AFloat>(),  //
                                               SinCases<f32>(),
                                               SinCases<f16>()))));
@@ -2512,7 +2512,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Sinh,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kSinh),
+    testing::Combine(testing::Values(builtin::Function::kSinh),
                      testing::ValuesIn(Concat(SinhCases<AFloat>(),  //
                                               SinhCases<f32>(),
                                               SinhCases<f16>()))));
@@ -2545,7 +2545,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Smoothstep,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kSmoothstep),
+    testing::Combine(testing::Values(builtin::Function::kSmoothstep),
                      testing::ValuesIn(Concat(SmoothstepCases<AFloat>(),  //
                                               SmoothstepCases<f32>(),
                                               SmoothstepCases<f16>()))));
@@ -2577,7 +2577,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Step,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kStep),
+    testing::Combine(testing::Values(builtin::Function::kStep),
                      testing::ValuesIn(Concat(StepCases<AFloat>(),  //
                                               StepCases<f32>(),
                                               StepCases<f16>()))));
@@ -2598,7 +2598,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Sqrt,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kSqrt),
+    testing::Combine(testing::Values(builtin::Function::kSqrt),
                      testing::ValuesIn(Concat(SqrtCases<AFloat>(),  //
                                               SqrtCases<f32>(),
                                               SqrtCases<f16>()))));
@@ -2617,7 +2617,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Tan,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kTan),
+    testing::Combine(testing::Values(builtin::Function::kTan),
                      testing::ValuesIn(Concat(TanCases<AFloat>(),  //
                                               TanCases<f32>(),
                                               TanCases<f16>()))));
@@ -2637,7 +2637,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Tanh,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kTanh),
+    testing::Combine(testing::Values(builtin::Function::kTanh),
                      testing::ValuesIn(Concat(TanhCases<AFloat>(),  //
                                               TanhCases<f32>(),
                                               TanhCases<f16>()))));
@@ -2703,7 +2703,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Transpose,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kTranspose),
+    testing::Combine(testing::Values(builtin::Function::kTranspose),
                      testing::ValuesIn(Concat(TransposeCases<AFloat>(),  //
                                               TransposeCases<f32>(),
                                               TransposeCases<f16>()))));
@@ -2721,7 +2721,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Trunc,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kTrunc),
+    testing::Combine(testing::Values(builtin::Function::kTrunc),
                      testing::ValuesIn(Concat(TruncCases<AFloat>(),  //
                                               TruncCases<f32>(),
                                               TruncCases<f16>()))));
@@ -2742,7 +2742,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Unpack4x8snorm,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kUnpack4X8Snorm),
+    testing::Combine(testing::Values(builtin::Function::kUnpack4X8Snorm),
                      testing::ValuesIn(Unpack4x8snormCases())));
 
 std::vector<Case> Unpack4x8unormCases() {
@@ -2759,7 +2759,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Unpack4x8unorm,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kUnpack4X8Unorm),
+    testing::Combine(testing::Values(builtin::Function::kUnpack4X8Unorm),
                      testing::ValuesIn(Unpack4x8unormCases())));
 
 std::vector<Case> Unpack2x16floatCases() {
@@ -2773,7 +2773,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Unpack2x16float,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kUnpack2X16Float),
+    testing::Combine(testing::Values(builtin::Function::kUnpack2X16Float),
                      testing::ValuesIn(Unpack2x16floatCases())));
 
 std::vector<Case> Unpack2x16snormCases() {
@@ -2791,7 +2791,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Unpack2x16snorm,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kUnpack2X16Snorm),
+    testing::Combine(testing::Values(builtin::Function::kUnpack2X16Snorm),
                      testing::ValuesIn(Unpack2x16snormCases())));
 
 std::vector<Case> Unpack2x16unormCases() {
@@ -2805,7 +2805,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     Unpack2x16unorm,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kUnpack2X16Unorm),
+    testing::Combine(testing::Values(builtin::Function::kUnpack2X16Unorm),
                      testing::ValuesIn(Unpack2x16unormCases())));
 
 std::vector<Case> QuantizeToF16Cases() {
@@ -2864,7 +2864,7 @@
 INSTANTIATE_TEST_SUITE_P(  //
     QuantizeToF16,
     ResolverConstEvalBuiltinTest,
-    testing::Combine(testing::Values(sem::BuiltinType::kQuantizeToF16),
+    testing::Combine(testing::Values(builtin::Function::kQuantizeToF16),
                      testing::ValuesIn(QuantizeToF16Cases())));
 
 }  // namespace
diff --git a/src/tint/resolver/const_eval_construction_test.cc b/src/tint/resolver/const_eval_construction_test.cc
index 55c2cf5..e98d21f 100644
--- a/src/tint/resolver/const_eval_construction_test.cc
+++ b/src/tint/resolver/const_eval_construction_test.cc
@@ -30,7 +30,6 @@
     ASSERT_NE(sem, nullptr);
     EXPECT_TRUE(sem->Type()->Is<type::AbstractFloat>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     EXPECT_EQ(sem->ConstantValue()->ValueAs<AFloat>(), 99.0f);
@@ -47,7 +46,6 @@
     ASSERT_NE(sem, nullptr);
     EXPECT_TRUE(sem->Type()->Is<type::AbstractInt>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     EXPECT_EQ(sem->ConstantValue()->ValueAs<AInt>(), 99);
@@ -63,7 +61,6 @@
     ASSERT_NE(sem, nullptr);
     EXPECT_TRUE(sem->Type()->Is<type::I32>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     EXPECT_EQ(sem->ConstantValue()->ValueAs<AInt>(), 99);
@@ -79,7 +76,6 @@
     ASSERT_NE(sem, nullptr);
     EXPECT_TRUE(sem->Type()->Is<type::U32>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     EXPECT_EQ(sem->ConstantValue()->ValueAs<AInt>(), 99u);
@@ -95,7 +91,6 @@
     ASSERT_NE(sem, nullptr);
     EXPECT_TRUE(sem->Type()->Is<type::F32>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     EXPECT_EQ(sem->ConstantValue()->ValueAs<AFloat>().value, 9.9f);
@@ -113,7 +108,6 @@
     EXPECT_NE(sem, nullptr);
     EXPECT_TRUE(sem->Type()->Is<type::F16>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     // 9.9 is not exactly representable by f16, and should be quantized to 9.8984375
@@ -130,7 +124,6 @@
     ASSERT_NE(sem, nullptr);
     EXPECT_TRUE(sem->Type()->Is<type::Bool>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     EXPECT_EQ(sem->ConstantValue()->ValueAs<bool>(), true);
@@ -158,7 +151,6 @@
     auto* sem = Sem().Get(expr);
     ASSERT_NE(sem, nullptr);
 
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
@@ -167,18 +159,15 @@
         EXPECT_EQ(sem->ConstantValue()->ValueAs<f32>(), 0.0f);
     } else if (auto* vec = sem->Type()->As<type::Vector>()) {
         for (size_t i = 0; i < vec->Width(); ++i) {
-            EXPECT_TRUE(sem->ConstantValue()->Index(i)->AllEqual());
             EXPECT_TRUE(sem->ConstantValue()->Index(i)->AnyZero());
             EXPECT_TRUE(sem->ConstantValue()->Index(i)->AllZero());
             EXPECT_EQ(sem->ConstantValue()->Index(i)->ValueAs<f32>(), 0.0f);
         }
     } else if (auto* mat = sem->Type()->As<type::Matrix>()) {
         for (size_t i = 0; i < mat->columns(); ++i) {
-            EXPECT_TRUE(sem->ConstantValue()->Index(i)->AllEqual());
             EXPECT_TRUE(sem->ConstantValue()->Index(i)->AnyZero());
             EXPECT_TRUE(sem->ConstantValue()->Index(i)->AllZero());
             for (size_t j = 0; j < mat->rows(); ++j) {
-                EXPECT_TRUE(sem->ConstantValue()->Index(i)->Index(j)->AllEqual());
                 EXPECT_TRUE(sem->ConstantValue()->Index(i)->Index(j)->AnyZero());
                 EXPECT_TRUE(sem->ConstantValue()->Index(i)->Index(j)->AllZero());
                 EXPECT_EQ(sem->ConstantValue()->Index(i)->Index(j)->ValueAs<f32>(), 0.0f);
@@ -186,7 +175,6 @@
         }
     } else if (auto* arr = sem->Type()->As<type::Array>()) {
         for (size_t i = 0; i < *(arr->ConstantCount()); ++i) {
-            EXPECT_TRUE(sem->ConstantValue()->Index(i)->AllEqual());
             EXPECT_TRUE(sem->ConstantValue()->Index(i)->AnyZero());
             EXPECT_TRUE(sem->ConstantValue()->Index(i)->AllZero());
             EXPECT_EQ(sem->ConstantValue()->Index(i)->ValueAs<f32>(), 0.0f);
@@ -229,21 +217,17 @@
     EXPECT_TRUE(vec->type()->Is<type::I32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 0);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 0);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 0);
@@ -262,21 +246,17 @@
     EXPECT_TRUE(vec->type()->Is<type::U32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 0u);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 0u);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 0u);
@@ -295,21 +275,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 0._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 0._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 0._a);
@@ -330,21 +306,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 0._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 0._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 0._a);
@@ -363,21 +335,17 @@
     EXPECT_TRUE(vec->type()->Is<type::Bool>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<bool>(), false);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<bool>(), false);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<bool>(), false);
@@ -396,21 +364,17 @@
     EXPECT_TRUE(vec->type()->Is<type::I32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 99);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 99);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 99);
@@ -429,21 +393,17 @@
     EXPECT_TRUE(vec->type()->Is<type::U32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 99u);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 99u);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 99u);
@@ -462,21 +422,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 9.9f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 9.9f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 9.9f);
@@ -497,22 +453,18 @@
     EXPECT_TRUE(vec->type()->Is<type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     // 9.9 is not exactly representable by f16, and should be quantized to 9.8984375
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 9.8984375f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 9.8984375f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 9.8984375f);
@@ -531,21 +483,17 @@
     EXPECT_TRUE(vec->type()->Is<type::Bool>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<bool>(), true);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<bool>(), true);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<bool>(), true);
@@ -565,21 +513,17 @@
     EXPECT_TRUE(vec->type()->Is<type::AbstractInt>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 1);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 2);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 3);
@@ -599,21 +543,17 @@
     EXPECT_TRUE(vec->type()->Is<type::AbstractFloat>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 1.0f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 2.0f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 3.0f);
@@ -632,21 +572,17 @@
     EXPECT_TRUE(vec->type()->Is<type::I32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 1);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 2);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 3);
@@ -665,21 +601,17 @@
     EXPECT_TRUE(vec->type()->Is<type::U32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 1);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 2);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 3);
@@ -698,21 +630,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 1.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 2.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 3.f);
@@ -733,21 +661,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 1.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 2.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 3.f);
@@ -766,21 +690,17 @@
     EXPECT_TRUE(vec->type()->Is<type::Bool>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<bool>(), true);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<bool>(), false);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<bool>(), true);
@@ -799,21 +719,17 @@
     EXPECT_TRUE(vec->type()->Is<type::I32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 1);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 2);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 3);
@@ -832,21 +748,17 @@
     EXPECT_TRUE(vec->type()->Is<type::U32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 1);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 2);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 3);
@@ -865,21 +777,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 1.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 2.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 3.f);
@@ -898,21 +806,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), 10_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), 10_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), 10_f);
@@ -931,21 +835,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), 0_f);
@@ -964,21 +864,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), -0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), -0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), -0_f);
@@ -997,21 +893,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), -0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), 0_f);
@@ -1032,21 +924,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 1.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 2.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 3.f);
@@ -1067,21 +955,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f16>(), 10_h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f16>(), 10_h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f16>(), 10_h);
@@ -1102,21 +986,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f16>(), 0_h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f16>(), 0_h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f16>(), 0_h);
@@ -1137,21 +1017,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f16>(), -0_h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f16>(), -0_h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f16>(), -0_h);
@@ -1172,21 +1048,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f16>(), 0_h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f16>(), -0_h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f16>(), 0_h);
@@ -1205,21 +1077,17 @@
     EXPECT_TRUE(vec->type()->Is<type::Bool>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<bool>(), true);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<bool>(), false);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<bool>(), true);
@@ -1238,21 +1106,17 @@
     EXPECT_TRUE(vec->type()->Is<type::Bool>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<bool>(), true);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<bool>(), true);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<bool>(), true);
@@ -1271,21 +1135,17 @@
     EXPECT_TRUE(vec->type()->Is<type::Bool>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<bool>(), false);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<bool>(), false);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<bool>(), false);
@@ -1305,36 +1165,29 @@
     EXPECT_EQ(mat->columns(), 2u);
     EXPECT_EQ(mat->rows(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<f32>(), 0._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<f32>(), 0._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(2)->ValueAs<f32>(), 0._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(0)->ValueAs<f32>(), 0._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(1)->ValueAs<f32>(), 0._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(2)->ValueAs<f32>(), 0._f);
@@ -1356,36 +1209,29 @@
     EXPECT_EQ(mat->columns(), 2u);
     EXPECT_EQ(mat->rows(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<f16>(), 0._h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<f16>(), 0._h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(2)->ValueAs<f16>(), 0._h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(0)->ValueAs<f16>(), 0._h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(1)->ValueAs<f16>(), 0._h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(2)->ValueAs<f16>(), 0._h);
@@ -1405,36 +1251,29 @@
     EXPECT_EQ(mat->columns(), 3u);
     EXPECT_EQ(mat->rows(), 2u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<AFloat>(), 1._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<AFloat>(), 2._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(0)->ValueAs<AFloat>(), 3._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(1)->ValueAs<AFloat>(), 4._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->Index(0)->ValueAs<AFloat>(), 5._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->Index(1)->ValueAs<AFloat>(), 6._a);
@@ -1457,36 +1296,29 @@
     EXPECT_EQ(mat->columns(), 3u);
     EXPECT_EQ(mat->rows(), 2u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<AFloat>(), 1._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<AFloat>(), 2._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(0)->ValueAs<AFloat>(), 3._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(1)->ValueAs<AFloat>(), 4._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->Index(0)->ValueAs<AFloat>(), 5._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->Index(1)->ValueAs<AFloat>(), 6._a);
@@ -1504,26 +1336,21 @@
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<type::I32>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<i32>(), 0_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<i32>(), 0_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<i32>(), 0_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(3)->ValueAs<i32>(), 0_i);
@@ -1541,26 +1368,21 @@
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<type::F32>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(3)->ValueAs<f32>(), 0_f);
@@ -1578,36 +1400,29 @@
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<type::Vector>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(2)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(0)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(1)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(2)->ValueAs<f32>(), 0_f);
@@ -1629,26 +1444,21 @@
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<sem::Struct>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(0)->ValueAs<f32>(), 0_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(1)->ValueAs<f32>(), 0_f);
@@ -1666,26 +1476,21 @@
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<type::I32>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<i32>(), 10_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<i32>(), 20_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<i32>(), 30_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(3)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(3)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(3)->ValueAs<i32>(), 40_i);
@@ -1765,17 +1570,13 @@
     EXPECT_TRUE(outer_arr->ElemType()->As<type::Array>()->ElemType()->Is<type::F32>());
 
     auto* arr = sem->ConstantValue();
-    EXPECT_FALSE(arr->AllEqual());
     EXPECT_FALSE(arr->AnyZero());
     EXPECT_FALSE(arr->AllZero());
 
-    EXPECT_FALSE(arr->Index(0)->AllEqual());
     EXPECT_FALSE(arr->Index(0)->AnyZero());
     EXPECT_FALSE(arr->Index(0)->AllZero());
-    EXPECT_FALSE(arr->Index(1)->AllEqual());
     EXPECT_FALSE(arr->Index(1)->AnyZero());
     EXPECT_FALSE(arr->Index(1)->AllZero());
-    EXPECT_FALSE(arr->Index(2)->AllEqual());
     EXPECT_FALSE(arr->Index(2)->AnyZero());
     EXPECT_FALSE(arr->Index(2)->AllZero());
 
@@ -1799,26 +1600,21 @@
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<type::F32>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), 10_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), 20_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), 30_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(3)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(3)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(3)->ValueAs<f32>(), 40_f);
@@ -1837,7 +1633,6 @@
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<type::Vector>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<f32>(), 1_f);
@@ -1866,26 +1661,21 @@
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<sem::Struct>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<f32>(), 1_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<f32>(), 2_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(0)->ValueAs<f32>(), 3_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(1)->ValueAs<f32>(), 4_f);
@@ -1914,7 +1704,6 @@
     EXPECT_EQ(str->Members().Length(), 5u);
 
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
@@ -1930,7 +1719,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(4)->ValueAs<bool>(), false);
 
     for (size_t i = 0; i < str->Members().Length(); ++i) {
-        EXPECT_TRUE(sem->ConstantValue()->Index(i)->AllEqual());
         EXPECT_TRUE(sem->ConstantValue()->Index(i)->AnyZero());
         EXPECT_TRUE(sem->ConstantValue()->Index(i)->AllZero());
     }
@@ -1962,12 +1750,10 @@
     ASSERT_NE(str, nullptr);
     EXPECT_EQ(str->Members().Length(), 1u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
     auto* inner_struct = sem->ConstantValue()->Index(0);
-    EXPECT_FALSE(inner_struct->AllEqual());
     EXPECT_TRUE(inner_struct->AnyZero());
     EXPECT_TRUE(inner_struct->AllZero());
 
@@ -1983,7 +1769,6 @@
     EXPECT_EQ(inner_struct->Index(4)->ValueAs<bool>(), false);
 
     for (size_t i = 0; i < str->Members().Length(); ++i) {
-        EXPECT_TRUE(inner_struct->Index(i)->AllEqual());
         EXPECT_TRUE(inner_struct->Index(i)->AnyZero());
         EXPECT_TRUE(inner_struct->Index(i)->AllZero());
     }
@@ -2004,23 +1789,19 @@
     EXPECT_EQ(str->Members().Length(), 3u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Type()->Is<type::I32>());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<i32>(), 0_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Type()->Is<type::I32>());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<i32>(), 0_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->Type()->Is<type::I32>());
@@ -2049,35 +1830,29 @@
     EXPECT_EQ(str->Members().Length(), 5u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Type()->Is<type::I32>());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<i32>(), 0_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Type()->Is<type::U32>());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<u32>(), 0_u);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->Type()->Is<type::F32>());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), 0._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->Type()->Is<type::F16>());
     EXPECT_EQ(sem->ConstantValue()->Index(3)->ValueAs<f16>(), 0._h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(4)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->Type()->Is<type::Bool>());
@@ -2102,11 +1877,9 @@
     EXPECT_EQ(str->Members().Length(), 3u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Type()->Is<type::Vector>());
@@ -2116,7 +1889,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<f32>(), 0._f);
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(2)->ValueAs<f32>(), 0._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Type()->Is<type::Vector>());
@@ -2126,7 +1898,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(1)->ValueAs<f32>(), 0._f);
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(2)->ValueAs<f32>(), 0._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->Type()->Is<type::Vector>());
@@ -2159,11 +1930,9 @@
     EXPECT_EQ(str->Members().Length(), 5u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Type()->Is<type::Vector>());
@@ -2172,7 +1941,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<i32>(), 0_i);
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<i32>(), 0_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Type()->Is<type::Vector>());
@@ -2182,7 +1950,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(1)->ValueAs<u32>(), 0_u);
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(2)->ValueAs<u32>(), 0_u);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->Type()->Is<type::Vector>());
@@ -2193,7 +1960,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(2)->Index(2)->ValueAs<f32>(), 0._f);
     EXPECT_EQ(sem->ConstantValue()->Index(2)->Index(3)->ValueAs<f32>(), 0._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->Type()->Is<type::Vector>());
@@ -2203,7 +1969,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(3)->Index(1)->ValueAs<f16>(), 0._h);
     EXPECT_EQ(sem->ConstantValue()->Index(3)->Index(2)->ValueAs<f16>(), 0._h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(4)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->Type()->Is<type::Vector>());
@@ -2236,11 +2001,9 @@
     EXPECT_EQ(str->Members().Length(), 2u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Type()->Is<sem::Struct>());
@@ -2248,7 +2011,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<u32>(), 0_u);
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(2)->ValueAs<f32>(), 0_f);
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Type()->Is<sem::Struct>());
@@ -2279,35 +2041,29 @@
     EXPECT_EQ(str->Members().Length(), 5u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Type()->Is<type::I32>());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<i32>(), 1_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Type()->Is<type::U32>());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<u32>(), 2_u);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->Type()->Is<type::F32>());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), 3._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(3)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(3)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->Type()->Is<type::F16>());
     EXPECT_EQ(sem->ConstantValue()->Index(3)->ValueAs<f16>(), 4._h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(4)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->Type()->Is<type::Bool>());
@@ -2337,11 +2093,9 @@
     EXPECT_EQ(str->Members().Length(), 5u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Type()->Is<type::Vector>());
@@ -2350,7 +2104,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<i32>(), 1_i);
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<i32>(), 1_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Type()->Is<type::Vector>());
@@ -2360,7 +2113,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(1)->ValueAs<u32>(), 2_u);
     EXPECT_EQ(sem->ConstantValue()->Index(1)->Index(2)->ValueAs<u32>(), 2_u);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->Type()->Is<type::Vector>());
@@ -2371,7 +2123,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(2)->Index(2)->ValueAs<f32>(), 3._f);
     EXPECT_EQ(sem->ConstantValue()->Index(2)->Index(3)->ValueAs<f32>(), 3._f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(3)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(3)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(3)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(3)->Type()->Is<type::Vector>());
@@ -2381,7 +2132,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(3)->Index(1)->ValueAs<f16>(), 4._h);
     EXPECT_EQ(sem->ConstantValue()->Index(3)->Index(2)->ValueAs<f16>(), 4._h);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(4)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(4)->Type()->Is<type::Vector>());
@@ -2415,11 +2165,9 @@
     EXPECT_EQ(str->Members().Length(), 2u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Type()->Is<sem::Struct>());
@@ -2427,7 +2175,6 @@
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<u32>(), 2_u);
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(2)->ValueAs<f32>(), 3_f);
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Type()->Is<sem::Struct>());
@@ -2454,18 +2201,15 @@
     EXPECT_EQ(str->Members().Length(), 2u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->Type()->Is<type::Array>());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(0)->ValueAs<i32>(), 1_i);
     EXPECT_EQ(sem->ConstantValue()->Index(0)->Index(1)->ValueAs<u32>(), 2_i);
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->Type()->Is<type::Array>());
diff --git a/src/tint/resolver/const_eval_conversion_test.cc b/src/tint/resolver/const_eval_conversion_test.cc
index 92af2de..350afb2 100644
--- a/src/tint/resolver/const_eval_conversion_test.cc
+++ b/src/tint/resolver/const_eval_conversion_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "src/tint/resolver/const_eval_test.h"
+#include "src/tint/sem/materialize.h"
 
 using namespace tint::number_suffixes;  // NOLINT
 
@@ -235,21 +236,17 @@
     EXPECT_TRUE(vec->type()->Is<type::I32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 1);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 2);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 3);
@@ -268,21 +265,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 10.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 20.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 30.f);
@@ -303,21 +296,17 @@
     EXPECT_TRUE(vec->type()->Is<type::I32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), 1_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), 2_i);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), 3_i);
@@ -338,21 +327,17 @@
     EXPECT_TRUE(vec->type()->Is<type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 10.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), 20.f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 30.f);
@@ -371,21 +356,17 @@
     EXPECT_TRUE(vec->type()->Is<type::I32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), i32::Highest());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), i32::Lowest());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), i32::Highest());
@@ -404,21 +385,17 @@
     EXPECT_TRUE(vec->type()->Is<type::U32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AInt>(), u32::Highest());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AInt>(), u32::Lowest());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AInt>(), u32::Highest());
@@ -449,28 +426,69 @@
     EXPECT_TRUE(vec->type()->Is<type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 0.0);
     EXPECT_FALSE(std::signbit(sem->ConstantValue()->Index(0)->ValueAs<AFloat>().value));
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), -0.0);
     EXPECT_TRUE(std::signbit(sem->ConstantValue()->Index(1)->ValueAs<AFloat>().value));
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 0.0);
     EXPECT_FALSE(std::signbit(sem->ConstantValue()->Index(2)->ValueAs<AFloat>().value));
 }
 
+TEST_F(ResolverConstEvalTest, StructAbstractSplat_to_StructDifferentTypes) {
+    // fn f() {
+    //   const c = modf(4.0);
+    //   var v = c;
+    // }
+    auto* expr_c = Call(builtin::Function::kModf, 0_a);
+    auto* materialized = Expr("c");
+    WrapInFunction(Decl(Const("c", expr_c)), Decl(Var("v", materialized)));
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* c = Sem().Get(expr_c);
+    ASSERT_NE(c, nullptr);
+    EXPECT_TRUE(c->ConstantValue()->Is<constant::Splat>());
+    EXPECT_TRUE(c->ConstantValue()->AnyZero());
+    EXPECT_TRUE(c->ConstantValue()->AllZero());
+
+    EXPECT_TRUE(c->ConstantValue()->Index(0)->AnyZero());
+    EXPECT_TRUE(c->ConstantValue()->Index(0)->AllZero());
+    EXPECT_TRUE(c->ConstantValue()->Index(0)->Type()->Is<type::AbstractFloat>());
+    EXPECT_EQ(c->ConstantValue()->Index(0)->ValueAs<AFloat>(), 0_f);
+
+    EXPECT_TRUE(c->ConstantValue()->Index(1)->AnyZero());
+    EXPECT_TRUE(c->ConstantValue()->Index(1)->AllZero());
+    EXPECT_TRUE(c->ConstantValue()->Index(1)->Type()->Is<type::AbstractFloat>());
+    EXPECT_EQ(c->ConstantValue()->Index(1)->ValueAs<AFloat>(), 0_a);
+
+    auto* v = Sem().GetVal(materialized);
+    ASSERT_NE(v, nullptr);
+    EXPECT_TRUE(v->Is<sem::Materialize>());
+    EXPECT_TRUE(v->ConstantValue()->Is<constant::Splat>());
+    EXPECT_TRUE(v->ConstantValue()->AnyZero());
+    EXPECT_TRUE(v->ConstantValue()->AllZero());
+
+    EXPECT_TRUE(v->ConstantValue()->Index(0)->AnyZero());
+    EXPECT_TRUE(v->ConstantValue()->Index(0)->AllZero());
+    EXPECT_TRUE(v->ConstantValue()->Index(0)->Type()->Is<type::F32>());
+    EXPECT_EQ(v->ConstantValue()->Index(0)->ValueAs<f32>(), 0_f);
+
+    EXPECT_TRUE(v->ConstantValue()->Index(1)->AnyZero());
+    EXPECT_TRUE(v->ConstantValue()->Index(1)->AllZero());
+    EXPECT_TRUE(v->ConstantValue()->Index(1)->Type()->Is<type::F32>());
+    EXPECT_EQ(v->ConstantValue()->Index(1)->ValueAs<f32>(), 0_f);
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/const_eval_indexing_test.cc b/src/tint/resolver/const_eval_indexing_test.cc
index 4d8c8cb..d311d30 100644
--- a/src/tint/resolver/const_eval_indexing_test.cc
+++ b/src/tint/resolver/const_eval_indexing_test.cc
@@ -29,7 +29,6 @@
     ASSERT_NE(sem, nullptr);
     ASSERT_TRUE(sem->Type()->Is<type::I32>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     EXPECT_EQ(sem->ConstantValue()->ValueAs<i32>(), 3_i);
@@ -133,7 +132,6 @@
     ASSERT_NE(sem, nullptr);
     ASSERT_TRUE(sem->Type()->Is<type::I32>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     EXPECT_EQ(sem->ConstantValue()->ValueAs<i32>(), 2_i);
@@ -152,12 +150,10 @@
     EXPECT_EQ(vec->Width(), 2u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), 3._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), 1._a);
@@ -174,7 +170,6 @@
     ASSERT_NE(sem, nullptr);
     ASSERT_TRUE(sem->Type()->Is<type::I32>());
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->AllZero());
     EXPECT_EQ(sem->ConstantValue()->ValueAs<i32>(), 2_i);
@@ -194,12 +189,10 @@
     EXPECT_EQ(vec->Width(), 2u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), 5._a);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), 6._a);
@@ -241,17 +234,14 @@
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), 4_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), 5_f);
 
-    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), 6_f);
@@ -310,36 +300,29 @@
         EXPECT_EQ(ty->columns(), 2u);
         EXPECT_EQ(ty->rows(), 3u);
         EXPECT_EQ(mat->ConstantValue()->Type(), mat->Type());
-        EXPECT_FALSE(mat->ConstantValue()->AllEqual());
         EXPECT_TRUE(mat->ConstantValue()->AnyZero());
         EXPECT_FALSE(mat->ConstantValue()->AllZero());
 
-        EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(0)->AllEqual());
         EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(0)->AnyZero());
         EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(0)->AllZero());
         EXPECT_EQ(mat->ConstantValue()->Index(0)->Index(0)->ValueAs<f32>(), 7_f);
 
-        EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(1)->AllEqual());
         EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(1)->AnyZero());
         EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(1)->AllZero());
         EXPECT_EQ(mat->ConstantValue()->Index(0)->Index(1)->ValueAs<f32>(), 0_f);
 
-        EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(2)->AllEqual());
         EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(2)->AnyZero());
         EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(2)->AllZero());
         EXPECT_EQ(mat->ConstantValue()->Index(0)->Index(2)->ValueAs<f32>(), 9_f);
 
-        EXPECT_TRUE(mat->ConstantValue()->Index(1)->Index(0)->AllEqual());
         EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(0)->AnyZero());
         EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(0)->AllZero());
         EXPECT_EQ(mat->ConstantValue()->Index(1)->Index(0)->ValueAs<f32>(), 10_f);
 
-        EXPECT_TRUE(mat->ConstantValue()->Index(1)->Index(1)->AllEqual());
         EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(1)->AnyZero());
         EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(1)->AllZero());
         EXPECT_EQ(mat->ConstantValue()->Index(1)->Index(1)->ValueAs<f32>(), 11_f);
 
-        EXPECT_TRUE(mat->ConstantValue()->Index(1)->Index(2)->AllEqual());
         EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(2)->AnyZero());
         EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(2)->AllZero());
         EXPECT_EQ(mat->ConstantValue()->Index(1)->Index(2)->ValueAs<f32>(), 12_f);
@@ -352,21 +335,17 @@
         EXPECT_TRUE(ty->type()->Is<type::F32>());
         EXPECT_EQ(ty->Width(), 3u);
         EXPECT_EQ(vec->ConstantValue()->Type(), vec->Type());
-        EXPECT_FALSE(vec->ConstantValue()->AllEqual());
         EXPECT_TRUE(vec->ConstantValue()->AnyZero());
         EXPECT_FALSE(vec->ConstantValue()->AllZero());
 
-        EXPECT_TRUE(vec->ConstantValue()->Index(0)->AllEqual());
         EXPECT_FALSE(vec->ConstantValue()->Index(0)->AnyZero());
         EXPECT_FALSE(vec->ConstantValue()->Index(0)->AllZero());
         EXPECT_EQ(vec->ConstantValue()->Index(0)->ValueAs<f32>(), 7_f);
 
-        EXPECT_TRUE(vec->ConstantValue()->Index(1)->AllEqual());
         EXPECT_TRUE(vec->ConstantValue()->Index(1)->AnyZero());
         EXPECT_TRUE(vec->ConstantValue()->Index(1)->AllZero());
         EXPECT_EQ(vec->ConstantValue()->Index(1)->ValueAs<f32>(), 0_f);
 
-        EXPECT_TRUE(vec->ConstantValue()->Index(2)->AllEqual());
         EXPECT_FALSE(vec->ConstantValue()->Index(2)->AnyZero());
         EXPECT_FALSE(vec->ConstantValue()->Index(2)->AllZero());
         EXPECT_EQ(vec->ConstantValue()->Index(2)->ValueAs<f32>(), 9_f);
@@ -376,7 +355,6 @@
         EXPECT_NE(f, nullptr);
         EXPECT_TRUE(f->Type()->Is<type::F32>());
         EXPECT_EQ(f->ConstantValue()->Type(), f->Type());
-        EXPECT_TRUE(f->ConstantValue()->AllEqual());
         EXPECT_FALSE(f->ConstantValue()->AnyZero());
         EXPECT_FALSE(f->ConstantValue()->AllZero());
         EXPECT_EQ(f->ConstantValue()->ValueAs<f32>(), 9_f);
diff --git a/src/tint/resolver/const_eval_member_access_test.cc b/src/tint/resolver/const_eval_member_access_test.cc
index 4fcba8b..9a74e6c 100644
--- a/src/tint/resolver/const_eval_member_access_test.cc
+++ b/src/tint/resolver/const_eval_member_access_test.cc
@@ -46,13 +46,11 @@
     EXPECT_EQ(str->Members().Length(), 2u);
     ASSERT_NE(outer->ConstantValue(), nullptr);
     EXPECT_TYPE(outer->ConstantValue()->Type(), outer->Type());
-    EXPECT_FALSE(outer->ConstantValue()->AllEqual());
     EXPECT_TRUE(outer->ConstantValue()->AnyZero());
     EXPECT_FALSE(outer->ConstantValue()->AllZero());
 
     auto* o1 = Sem().Get(o1_expr);
     ASSERT_NE(o1->ConstantValue(), nullptr);
-    EXPECT_FALSE(o1->ConstantValue()->AllEqual());
     EXPECT_FALSE(o1->ConstantValue()->AnyZero());
     EXPECT_FALSE(o1->ConstantValue()->AllZero());
     EXPECT_TRUE(o1->ConstantValue()->Type()->Is<sem::Struct>());
@@ -63,7 +61,6 @@
 
     auto* i2 = Sem().Get(i2_expr);
     ASSERT_NE(i2->ConstantValue(), nullptr);
-    EXPECT_TRUE(i2->ConstantValue()->AllEqual());
     EXPECT_FALSE(i2->ConstantValue()->AnyZero());
     EXPECT_FALSE(i2->ConstantValue()->AllZero());
     EXPECT_TRUE(i2->ConstantValue()->Type()->Is<type::U32>());
@@ -85,7 +82,6 @@
     EXPECT_TYPE(cv->Type(), sem->Type());
     EXPECT_TRUE(cv->Index(0)->Type()->Is<type::Vector>());
     EXPECT_TRUE(cv->Index(0)->Index(0)->Type()->Is<type::AbstractFloat>());
-    EXPECT_FALSE(cv->AllEqual());
     EXPECT_FALSE(cv->AnyZero());
     EXPECT_FALSE(cv->AllZero());
     auto* c0 = cv->Index(0);
@@ -124,7 +120,6 @@
     EXPECT_TYPE(cv->Type(), sem->Type());
     EXPECT_TRUE(cv->Index(0)->Type()->Is<type::Vector>());
     EXPECT_TRUE(cv->Index(0)->Index(0)->Type()->Is<type::AbstractFloat>());
-    EXPECT_FALSE(cv->AllEqual());
     EXPECT_FALSE(cv->AnyZero());
     EXPECT_FALSE(cv->AllZero());
 
@@ -201,7 +196,6 @@
     EXPECT_TYPE(cv->Type(), sem->Type());
     EXPECT_TRUE(cv->Index(0)->Type()->Is<type::Vector>());
     EXPECT_TRUE(cv->Index(0)->Index(0)->Type()->Is<type::F32>());
-    EXPECT_FALSE(cv->AllEqual());
     EXPECT_FALSE(cv->AnyZero());
     EXPECT_FALSE(cv->AllZero());
 
diff --git a/src/tint/resolver/const_eval_test.h b/src/tint/resolver/const_eval_test.h
index de25b20..8cbea04 100644
--- a/src/tint/resolver/const_eval_test.h
+++ b/src/tint/resolver/const_eval_test.h
@@ -23,6 +23,7 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/test_helper.h"
 #include "src/tint/utils/string_stream.h"
 
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index f718a46..47e691c 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -60,6 +60,7 @@
 #include "src/tint/builtin/builtin_value.h"
 #include "src/tint/scope_stack.h"
 #include "src/tint/sem/builtin.h"
+#include "src/tint/switch.h"
 #include "src/tint/symbol_table.h"
 #include "src/tint/utils/block_allocator.h"
 #include "src/tint/utils/compiler_macros.h"
@@ -440,7 +441,8 @@
         auto* resolved = scope_stack_.Get(to);
         if (!resolved) {
             auto s = symbols_.NameFor(to);
-            if (auto builtin_fn = sem::ParseBuiltinType(s); builtin_fn != sem::BuiltinType::kNone) {
+            if (auto builtin_fn = builtin::ParseFunction(s);
+                builtin_fn != builtin::Function::kNone) {
                 graph_.resolved_identifiers.Add(from, ResolvedIdentifier(builtin_fn));
                 return;
             }
@@ -838,7 +840,7 @@
                 return "<unknown>";
             });
     }
-    if (auto builtin_fn = BuiltinFunction(); builtin_fn != sem::BuiltinType::kNone) {
+    if (auto builtin_fn = BuiltinFunction(); builtin_fn != builtin::Function::kNone) {
         return "builtin function '" + utils::ToString(builtin_fn) + "'";
     }
     if (auto builtin_ty = BuiltinType(); builtin_ty != builtin::Builtin::kUndefined) {
diff --git a/src/tint/resolver/dependency_graph.h b/src/tint/resolver/dependency_graph.h
index 429a47a..f58b2a2 100644
--- a/src/tint/resolver/dependency_graph.h
+++ b/src/tint/resolver/dependency_graph.h
@@ -22,11 +22,11 @@
 #include "src/tint/builtin/access.h"
 #include "src/tint/builtin/builtin.h"
 #include "src/tint/builtin/builtin_value.h"
+#include "src/tint/builtin/function.h"
 #include "src/tint/builtin/interpolation_sampling.h"
 #include "src/tint/builtin/interpolation_type.h"
 #include "src/tint/builtin/texel_format.h"
 #include "src/tint/diagnostic/diagnostic.h"
-#include "src/tint/sem/builtin_type.h"
 #include "src/tint/symbol_table.h"
 #include "src/tint/utils/hashmap.h"
 
@@ -44,7 +44,7 @@
 /// - const ast::TypeDecl*  (as const ast::Node*)
 /// - const ast::Variable*  (as const ast::Node*)
 /// - const ast::Function*  (as const ast::Node*)
-/// - sem::BuiltinType
+/// - builtin::Function
 /// - builtin::Access
 /// - builtin::AddressSpace
 /// - builtin::Builtin
@@ -75,13 +75,13 @@
         return nullptr;
     }
 
-    /// @return the builtin function if the ResolvedIdentifier holds sem::BuiltinType, otherwise
-    /// sem::BuiltinType::kNone
-    sem::BuiltinType BuiltinFunction() const {
-        if (auto n = std::get_if<sem::BuiltinType>(&value_)) {
+    /// @return the builtin function if the ResolvedIdentifier holds builtin::Function, otherwise
+    /// builtin::Function::kNone
+    builtin::Function BuiltinFunction() const {
+        if (auto n = std::get_if<builtin::Function>(&value_)) {
             return *n;
         }
-        return sem::BuiltinType::kNone;
+        return builtin::Function::kNone;
     }
 
     /// @return the access if the ResolvedIdentifier holds builtin::Access, otherwise
@@ -172,7 +172,7 @@
   private:
     std::variant<UnresolvedIdentifier,
                  const ast::Node*,
-                 sem::BuiltinType,
+                 builtin::Function,
                  builtin::Access,
                  builtin::AddressSpace,
                  builtin::Builtin,
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
index c81c336..c9ce3b8 100644
--- a/src/tint/resolver/dependency_graph_test.cc
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -1181,7 +1181,7 @@
 namespace resolve_to_builtin_func {
 
 using ResolverDependencyGraphResolveToBuiltinFunc =
-    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, sem::BuiltinType>>;
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, builtin::Function>>;
 
 TEST_P(ResolverDependencyGraphResolveToBuiltinFunc, Resolve) {
     const auto use = std::get<0>(GetParam());
@@ -1200,17 +1200,17 @@
 INSTANTIATE_TEST_SUITE_P(Types,
                          ResolverDependencyGraphResolveToBuiltinFunc,
                          testing::Combine(testing::ValuesIn(kTypeUseKinds),
-                                          testing::ValuesIn(sem::kBuiltinTypes)));
+                                          testing::ValuesIn(builtin::kFunctions)));
 
 INSTANTIATE_TEST_SUITE_P(Values,
                          ResolverDependencyGraphResolveToBuiltinFunc,
                          testing::Combine(testing::ValuesIn(kValueUseKinds),
-                                          testing::ValuesIn(sem::kBuiltinTypes)));
+                                          testing::ValuesIn(builtin::kFunctions)));
 
 INSTANTIATE_TEST_SUITE_P(Functions,
                          ResolverDependencyGraphResolveToBuiltinFunc,
                          testing::Combine(testing::ValuesIn(kFuncUseKinds),
-                                          testing::ValuesIn(sem::kBuiltinTypes)));
+                                          testing::ValuesIn(builtin::kFunctions)));
 
 }  // namespace resolve_to_builtin_func
 
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index e8fe271..18e2f81 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -24,6 +24,7 @@
 #include "src/tint/sem/pipeline_stage_set.h"
 #include "src/tint/sem/value_constructor.h"
 #include "src/tint/sem/value_conversion.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/abstract_float.h"
 #include "src/tint/type/abstract_int.h"
 #include "src/tint/type/abstract_numeric.h"
@@ -1096,7 +1097,7 @@
   public:
     explicit Impl(ProgramBuilder& builder);
 
-    Builtin Lookup(sem::BuiltinType builtin_type,
+    Builtin Lookup(builtin::Function builtin_type,
                    utils::VectorRef<const type::Type*> args,
                    sem::EvaluationStage earliest_eval_stage,
                    const Source& source) override;
@@ -1264,11 +1265,11 @@
 
 Impl::Impl(ProgramBuilder& b) : builder(b) {}
 
-Impl::Builtin Impl::Lookup(sem::BuiltinType builtin_type,
+Impl::Builtin Impl::Lookup(builtin::Function builtin_type,
                            utils::VectorRef<const type::Type*> args,
                            sem::EvaluationStage earliest_eval_stage,
                            const Source& source) {
-    const char* intrinsic_name = sem::str(builtin_type);
+    const char* intrinsic_name = builtin::str(builtin_type);
 
     // Generates an error when no overloads match the provided arguments
     auto on_no_match = [&](utils::VectorRef<Candidate> candidates) {
diff --git a/src/tint/resolver/intrinsic_table.h b/src/tint/resolver/intrinsic_table.h
index 515e839..78e2e7a 100644
--- a/src/tint/resolver/intrinsic_table.h
+++ b/src/tint/resolver/intrinsic_table.h
@@ -92,7 +92,7 @@
     ///        after shader creation time (sem::EvaluationStage::kConstant).
     /// @param source the source of the builtin call
     /// @return the semantic builtin if found, otherwise nullptr
-    virtual Builtin Lookup(sem::BuiltinType type,
+    virtual Builtin Lookup(builtin::Function type,
                            utils::VectorRef<const type::Type*> args,
                            sem::EvaluationStage earliest_eval_stage,
                            const Source& source) = 0;
diff --git a/src/tint/resolver/intrinsic_table.inl b/src/tint/resolver/intrinsic_table.inl
index 1683d27..895d489 100644
--- a/src/tint/resolver/intrinsic_table.inl
+++ b/src/tint/resolver/intrinsic_table.inl
@@ -7748,102 +7748,102 @@
   {
     /* [931] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[235],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [932] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[58],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [933] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[116],
+    /* matcher indices */ &kMatcherIndices[237],
   },
   {
     /* [934] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[118],
+    /* matcher indices */ &kMatcherIndices[58],
   },
   {
     /* [935] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[122],
+    /* matcher indices */ &kMatcherIndices[116],
   },
   {
     /* [936] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[118],
   },
   {
     /* [937] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[122],
   },
   {
     /* [938] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[130],
+    /* matcher indices */ &kMatcherIndices[124],
   },
   {
     /* [939] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[234],
+    /* matcher indices */ &kMatcherIndices[128],
   },
   {
     /* [940] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[235],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [941] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[236],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [942] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[237],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [943] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[132],
+    /* matcher indices */ &kMatcherIndices[236],
   },
   {
     /* [944] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[238],
+    /* matcher indices */ &kMatcherIndices[237],
   },
   {
     /* [945] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[5],
+    /* usage */ ParameterUsage::kTexture,
+    /* matcher indices */ &kMatcherIndices[132],
   },
   {
     /* [946] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* usage */ ParameterUsage::kTexture,
+    /* matcher indices */ &kMatcherIndices[238],
   },
   {
     /* [947] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[39],
+    /* matcher indices */ &kMatcherIndices[5],
   },
   {
     /* [948] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[37],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [949] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[39],
   },
   {
     /* [950] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[34],
+    /* matcher indices */ &kMatcherIndices[37],
   },
   {
     /* [951] */
@@ -7858,17 +7858,17 @@
   {
     /* [953] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[9],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [954] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[34],
   },
   {
     /* [955] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[105],
+    /* matcher indices */ &kMatcherIndices[9],
   },
   {
     /* [956] */
@@ -7878,7 +7878,7 @@
   {
     /* [957] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[42],
+    /* matcher indices */ &kMatcherIndices[105],
   },
   {
     /* [958] */
@@ -7888,7 +7888,7 @@
   {
     /* [959] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[1],
+    /* matcher indices */ &kMatcherIndices[42],
   },
   {
     /* [960] */
@@ -7898,7 +7898,7 @@
   {
     /* [961] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[39],
+    /* matcher indices */ &kMatcherIndices[1],
   },
   {
     /* [962] */
@@ -7908,7 +7908,7 @@
   {
     /* [963] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[23],
+    /* matcher indices */ &kMatcherIndices[39],
   },
   {
     /* [964] */
@@ -7918,12 +7918,12 @@
   {
     /* [965] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[158],
+    /* matcher indices */ &kMatcherIndices[23],
   },
   {
     /* [966] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[158],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [967] */
@@ -7943,22 +7943,22 @@
   {
     /* [970] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[106],
+    /* matcher indices */ &kMatcherIndices[158],
   },
   {
     /* [971] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[158],
   },
   {
     /* [972] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[160],
+    /* matcher indices */ &kMatcherIndices[106],
   },
   {
     /* [973] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[160],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [974] */
@@ -7978,22 +7978,22 @@
   {
     /* [977] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[134],
+    /* matcher indices */ &kMatcherIndices[160],
   },
   {
     /* [978] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[160],
   },
   {
     /* [979] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[172],
+    /* matcher indices */ &kMatcherIndices[134],
   },
   {
     /* [980] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[172],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [981] */
@@ -8013,141 +8013,151 @@
   {
     /* [984] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[178],
+    /* matcher indices */ &kMatcherIndices[172],
   },
   {
     /* [985] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[180],
+    /* matcher indices */ &kMatcherIndices[172],
   },
   {
     /* [986] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[182],
+    /* matcher indices */ &kMatcherIndices[178],
   },
   {
     /* [987] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[184],
+    /* matcher indices */ &kMatcherIndices[180],
   },
   {
     /* [988] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[186],
+    /* matcher indices */ &kMatcherIndices[182],
   },
   {
     /* [989] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[188],
+    /* matcher indices */ &kMatcherIndices[184],
   },
   {
     /* [990] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[190],
+    /* matcher indices */ &kMatcherIndices[186],
   },
   {
     /* [991] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[188],
   },
   {
     /* [992] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[190],
   },
   {
     /* [993] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[192],
   },
   {
     /* [994] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[198],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [995] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[200],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [996] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[202],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [997] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[204],
+    /* matcher indices */ &kMatcherIndices[200],
   },
   {
     /* [998] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[206],
+    /* matcher indices */ &kMatcherIndices[202],
   },
   {
     /* [999] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[208],
+    /* matcher indices */ &kMatcherIndices[204],
   },
   {
     /* [1000] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[210],
+    /* matcher indices */ &kMatcherIndices[206],
   },
   {
     /* [1001] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[212],
+    /* matcher indices */ &kMatcherIndices[208],
   },
   {
     /* [1002] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[214],
+    /* matcher indices */ &kMatcherIndices[210],
   },
   {
     /* [1003] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[216],
+    /* matcher indices */ &kMatcherIndices[212],
   },
   {
     /* [1004] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[218],
+    /* matcher indices */ &kMatcherIndices[214],
   },
   {
     /* [1005] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[220],
+    /* matcher indices */ &kMatcherIndices[216],
   },
   {
     /* [1006] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[222],
+    /* matcher indices */ &kMatcherIndices[218],
   },
   {
     /* [1007] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[224],
+    /* matcher indices */ &kMatcherIndices[220],
   },
   {
     /* [1008] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[226],
+    /* matcher indices */ &kMatcherIndices[222],
   },
   {
     /* [1009] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[228],
+    /* matcher indices */ &kMatcherIndices[224],
   },
   {
     /* [1010] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[230],
+    /* matcher indices */ &kMatcherIndices[226],
   },
   {
     /* [1011] */
     /* usage */ ParameterUsage::kNone,
+    /* matcher indices */ &kMatcherIndices[228],
+  },
+  {
+    /* [1012] */
+    /* usage */ ParameterUsage::kNone,
+    /* matcher indices */ &kMatcherIndices[230],
+  },
+  {
+    /* [1013] */
+    /* usage */ ParameterUsage::kNone,
     /* matcher indices */ &kMatcherIndices[106],
   },
 };
@@ -8910,7 +8920,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[36],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[134],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -8922,7 +8932,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[977],
+    /* parameters */ &kParameters[979],
     /* return matcher indices */ &kMatcherIndices[134],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -8934,7 +8944,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[978],
+    /* parameters */ &kParameters[980],
     /* return matcher indices */ &kMatcherIndices[134],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::VecSplat,
@@ -9030,7 +9040,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[979],
+    /* parameters */ &kParameters[981],
     /* return matcher indices */ &kMatcherIndices[114],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -9042,7 +9052,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[980],
+    /* parameters */ &kParameters[982],
     /* return matcher indices */ &kMatcherIndices[174],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -9054,7 +9064,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[981],
+    /* parameters */ &kParameters[983],
     /* return matcher indices */ &kMatcherIndices[154],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -9066,7 +9076,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[982],
+    /* parameters */ &kParameters[984],
     /* return matcher indices */ &kMatcherIndices[156],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -9078,7 +9088,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[22],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[983],
+    /* parameters */ &kParameters[985],
     /* return matcher indices */ &kMatcherIndices[176],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -9546,7 +9556,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[36],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[106],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -9558,7 +9568,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[970],
+    /* parameters */ &kParameters[972],
     /* return matcher indices */ &kMatcherIndices[106],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -9570,7 +9580,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[971],
+    /* parameters */ &kParameters[973],
     /* return matcher indices */ &kMatcherIndices[106],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::VecSplat,
@@ -9618,7 +9628,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[972],
+    /* parameters */ &kParameters[974],
     /* return matcher indices */ &kMatcherIndices[138],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -9630,7 +9640,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[973],
+    /* parameters */ &kParameters[975],
     /* return matcher indices */ &kMatcherIndices[168],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -9642,7 +9652,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[974],
+    /* parameters */ &kParameters[976],
     /* return matcher indices */ &kMatcherIndices[148],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -9654,7 +9664,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[975],
+    /* parameters */ &kParameters[977],
     /* return matcher indices */ &kMatcherIndices[126],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -9666,7 +9676,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[22],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[976],
+    /* parameters */ &kParameters[978],
     /* return matcher indices */ &kMatcherIndices[170],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -9678,7 +9688,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[933],
+    /* parameters */ &kParameters[935],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
@@ -9690,7 +9700,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[934],
+    /* parameters */ &kParameters[936],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
@@ -9702,7 +9712,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[935],
+    /* parameters */ &kParameters[937],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
@@ -9714,7 +9724,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[936],
+    /* parameters */ &kParameters[938],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
@@ -9726,7 +9736,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[937],
+    /* parameters */ &kParameters[939],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
@@ -9738,7 +9748,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[938],
+    /* parameters */ &kParameters[940],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
@@ -9750,7 +9760,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[939],
+    /* parameters */ &kParameters[941],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
@@ -9762,7 +9772,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[940],
+    /* parameters */ &kParameters[942],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
@@ -9774,7 +9784,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[941],
+    /* parameters */ &kParameters[943],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
@@ -9786,7 +9796,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[942],
+    /* parameters */ &kParameters[944],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
@@ -10014,7 +10024,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[36],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[23],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -10026,7 +10036,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[963],
+    /* parameters */ &kParameters[965],
     /* return matcher indices */ &kMatcherIndices[23],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -10038,7 +10048,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[964],
+    /* parameters */ &kParameters[966],
     /* return matcher indices */ &kMatcherIndices[23],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::VecSplat,
@@ -10062,7 +10072,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[965],
+    /* parameters */ &kParameters[967],
     /* return matcher indices */ &kMatcherIndices[112],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10074,7 +10084,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[966],
+    /* parameters */ &kParameters[968],
     /* return matcher indices */ &kMatcherIndices[164],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10086,7 +10096,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[967],
+    /* parameters */ &kParameters[969],
     /* return matcher indices */ &kMatcherIndices[136],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10098,7 +10108,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[968],
+    /* parameters */ &kParameters[970],
     /* return matcher indices */ &kMatcherIndices[120],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10110,7 +10120,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[22],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[969],
+    /* parameters */ &kParameters[971],
     /* return matcher indices */ &kMatcherIndices[166],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10530,7 +10540,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[178],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -10542,7 +10552,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[984],
+    /* parameters */ &kParameters[986],
     /* return matcher indices */ &kMatcherIndices[178],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -10578,7 +10588,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[985],
+    /* parameters */ &kParameters[987],
     /* return matcher indices */ &kMatcherIndices[182],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10590,7 +10600,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[986],
+    /* parameters */ &kParameters[988],
     /* return matcher indices */ &kMatcherIndices[180],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10602,7 +10612,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[184],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -10614,7 +10624,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[987],
+    /* parameters */ &kParameters[989],
     /* return matcher indices */ &kMatcherIndices[184],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -10650,7 +10660,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[988],
+    /* parameters */ &kParameters[990],
     /* return matcher indices */ &kMatcherIndices[188],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10662,7 +10672,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[989],
+    /* parameters */ &kParameters[991],
     /* return matcher indices */ &kMatcherIndices[186],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10674,7 +10684,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[190],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -10686,7 +10696,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[990],
+    /* parameters */ &kParameters[992],
     /* return matcher indices */ &kMatcherIndices[190],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -10722,7 +10732,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[991],
+    /* parameters */ &kParameters[993],
     /* return matcher indices */ &kMatcherIndices[194],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10734,7 +10744,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[992],
+    /* parameters */ &kParameters[994],
     /* return matcher indices */ &kMatcherIndices[192],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10746,7 +10756,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[196],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -10758,7 +10768,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[993],
+    /* parameters */ &kParameters[995],
     /* return matcher indices */ &kMatcherIndices[196],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -10794,7 +10804,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[994],
+    /* parameters */ &kParameters[996],
     /* return matcher indices */ &kMatcherIndices[200],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10806,7 +10816,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[995],
+    /* parameters */ &kParameters[997],
     /* return matcher indices */ &kMatcherIndices[198],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10818,7 +10828,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[202],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -10830,7 +10840,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[996],
+    /* parameters */ &kParameters[998],
     /* return matcher indices */ &kMatcherIndices[202],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -10866,7 +10876,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[997],
+    /* parameters */ &kParameters[999],
     /* return matcher indices */ &kMatcherIndices[206],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10878,7 +10888,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[998],
+    /* parameters */ &kParameters[1000],
     /* return matcher indices */ &kMatcherIndices[204],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10890,7 +10900,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[208],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -10902,7 +10912,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[999],
+    /* parameters */ &kParameters[1001],
     /* return matcher indices */ &kMatcherIndices[208],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -10938,7 +10948,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1000],
+    /* parameters */ &kParameters[1002],
     /* return matcher indices */ &kMatcherIndices[212],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10950,7 +10960,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1001],
+    /* parameters */ &kParameters[1003],
     /* return matcher indices */ &kMatcherIndices[210],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -10962,7 +10972,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[214],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -10974,7 +10984,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1002],
+    /* parameters */ &kParameters[1004],
     /* return matcher indices */ &kMatcherIndices[214],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -11010,7 +11020,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1003],
+    /* parameters */ &kParameters[1005],
     /* return matcher indices */ &kMatcherIndices[218],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -11022,7 +11032,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1004],
+    /* parameters */ &kParameters[1006],
     /* return matcher indices */ &kMatcherIndices[216],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -11034,7 +11044,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[220],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -11046,7 +11056,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1005],
+    /* parameters */ &kParameters[1007],
     /* return matcher indices */ &kMatcherIndices[220],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -11082,7 +11092,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1006],
+    /* parameters */ &kParameters[1008],
     /* return matcher indices */ &kMatcherIndices[224],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -11094,7 +11104,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1007],
+    /* parameters */ &kParameters[1009],
     /* return matcher indices */ &kMatcherIndices[222],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -11106,7 +11116,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ &kMatcherIndices[226],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
@@ -11118,7 +11128,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1008],
+    /* parameters */ &kParameters[1010],
     /* return matcher indices */ &kMatcherIndices[226],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
@@ -11154,7 +11164,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1009],
+    /* parameters */ &kParameters[1011],
     /* return matcher indices */ &kMatcherIndices[230],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -11166,397 +11176,13 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1010],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[228],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
   },
   {
     /* [231] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[672],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpPlus,
-  },
-  {
-    /* [232] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[674],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpPlus,
-  },
-  {
-    /* [233] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[676],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpPlus,
-  },
-  {
-    /* [234] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[678],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpPlus,
-  },
-  {
-    /* [235] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 2,
-    /* template types */ &kTemplateTypes[10],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[680],
-    /* return matcher indices */ &kMatcherIndices[14],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpPlus,
-  },
-  {
-    /* [236] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[682],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpMinus,
-  },
-  {
-    /* [237] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[684],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpMinus,
-  },
-  {
-    /* [238] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[686],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpMinus,
-  },
-  {
-    /* [239] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[688],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpMinus,
-  },
-  {
-    /* [240] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 2,
-    /* template types */ &kTemplateTypes[10],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[690],
-    /* return matcher indices */ &kMatcherIndices[14],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpMinus,
-  },
-  {
-    /* [241] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[710],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpDivide,
-  },
-  {
-    /* [242] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[712],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpDivide,
-  },
-  {
-    /* [243] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[714],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpDivide,
-  },
-  {
-    /* [244] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[716],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpDivide,
-  },
-  {
-    /* [245] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[718],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpModulo,
-  },
-  {
-    /* [246] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[720],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpModulo,
-  },
-  {
-    /* [247] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[722],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpModulo,
-  },
-  {
-    /* [248] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[24],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[724],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpModulo,
-  },
-  {
-    /* [249] */
-    /* num parameters */ 2,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[730],
-    /* return matcher indices */ &kMatcherIndices[39],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpAnd,
-  },
-  {
-    /* [250] */
-    /* num parameters */ 2,
-    /* num template types */ 0,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[732],
-    /* return matcher indices */ &kMatcherIndices[37],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpAnd,
-  },
-  {
-    /* [251] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[29],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[734],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpAnd,
-  },
-  {
-    /* [252] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[29],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[736],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpAnd,
-  },
-  {
-    /* [253] */
-    /* num parameters */ 2,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[738],
-    /* return matcher indices */ &kMatcherIndices[39],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpOr,
-  },
-  {
-    /* [254] */
-    /* num parameters */ 2,
-    /* num template types */ 0,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[740],
-    /* return matcher indices */ &kMatcherIndices[37],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpOr,
-  },
-  {
-    /* [255] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[29],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[742],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpOr,
-  },
-  {
-    /* [256] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[29],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[744],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpOr,
-  },
-  {
-    /* [257] */
-    /* num parameters */ 3,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[10],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[468],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::mix,
-  },
-  {
-    /* [258] */
-    /* num parameters */ 3,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[471],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::mix,
-  },
-  {
-    /* [259] */
-    /* num parameters */ 3,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[10],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[474],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::mix,
-  },
-  {
-    /* [260] */
-    /* num parameters */ 3,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[27],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[480],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::select_bool,
-  },
-  {
-    /* [261] */
-    /* num parameters */ 3,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[27],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[483],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::select_bool,
-  },
-  {
-    /* [262] */
-    /* num parameters */ 3,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[27],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[486],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::select_boolvec,
-  },
-  {
-    /* [263] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11568,11 +11194,11 @@
     /* const eval */ nullptr,
   },
   {
-    /* [264] */
+    /* [232] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[931],
     /* return matcher indices */ &kMatcherIndices[105],
@@ -11580,199 +11206,607 @@
     /* const eval */ nullptr,
   },
   {
-    /* [265] */
+    /* [233] */
     /* num parameters */ 1,
     /* num template types */ 0,
-    /* num template numbers */ 2,
+    /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[6],
+    /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[932],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
   },
   {
-    /* [266] */
-    /* num parameters */ 0,
+    /* [234] */
+    /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
-    /* return matcher indices */ &kMatcherIndices[9],
-    /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::Zero,
+    /* parameters */ &kParameters[933],
+    /* return matcher indices */ &kMatcherIndices[105],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ nullptr,
+  },
+  {
+    /* [235] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 2,
+    /* template types */ &kTemplateTypes[38],
+    /* template numbers */ &kTemplateNumbers[6],
+    /* parameters */ &kParameters[934],
+    /* return matcher indices */ &kMatcherIndices[105],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ nullptr,
+  },
+  {
+    /* [236] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[672],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpPlus,
+  },
+  {
+    /* [237] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[674],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpPlus,
+  },
+  {
+    /* [238] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[676],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpPlus,
+  },
+  {
+    /* [239] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[678],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpPlus,
+  },
+  {
+    /* [240] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 2,
+    /* template types */ &kTemplateTypes[10],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[680],
+    /* return matcher indices */ &kMatcherIndices[14],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpPlus,
+  },
+  {
+    /* [241] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[682],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpMinus,
+  },
+  {
+    /* [242] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[684],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpMinus,
+  },
+  {
+    /* [243] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[686],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpMinus,
+  },
+  {
+    /* [244] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[688],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpMinus,
+  },
+  {
+    /* [245] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 2,
+    /* template types */ &kTemplateTypes[10],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[690],
+    /* return matcher indices */ &kMatcherIndices[14],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpMinus,
+  },
+  {
+    /* [246] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[710],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpDivide,
+  },
+  {
+    /* [247] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[712],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpDivide,
+  },
+  {
+    /* [248] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[714],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpDivide,
+  },
+  {
+    /* [249] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[716],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpDivide,
+  },
+  {
+    /* [250] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[718],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpModulo,
+  },
+  {
+    /* [251] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[720],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpModulo,
+  },
+  {
+    /* [252] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[722],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpModulo,
+  },
+  {
+    /* [253] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[24],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[724],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpModulo,
+  },
+  {
+    /* [254] */
+    /* num parameters */ 2,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[38],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[730],
+    /* return matcher indices */ &kMatcherIndices[39],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpAnd,
+  },
+  {
+    /* [255] */
+    /* num parameters */ 2,
+    /* num template types */ 0,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[38],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[732],
+    /* return matcher indices */ &kMatcherIndices[37],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpAnd,
+  },
+  {
+    /* [256] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[29],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[734],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpAnd,
+  },
+  {
+    /* [257] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[29],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[736],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpAnd,
+  },
+  {
+    /* [258] */
+    /* num parameters */ 2,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[38],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[738],
+    /* return matcher indices */ &kMatcherIndices[39],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpOr,
+  },
+  {
+    /* [259] */
+    /* num parameters */ 2,
+    /* num template types */ 0,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[38],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[740],
+    /* return matcher indices */ &kMatcherIndices[37],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpOr,
+  },
+  {
+    /* [260] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[29],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[742],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpOr,
+  },
+  {
+    /* [261] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[29],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[744],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpOr,
+  },
+  {
+    /* [262] */
+    /* num parameters */ 3,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[10],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[468],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::mix,
+  },
+  {
+    /* [263] */
+    /* num parameters */ 3,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[10],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[471],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::mix,
+  },
+  {
+    /* [264] */
+    /* num parameters */ 3,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[10],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[474],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::mix,
+  },
+  {
+    /* [265] */
+    /* num parameters */ 3,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[27],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[480],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::select_bool,
+  },
+  {
+    /* [266] */
+    /* num parameters */ 3,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[27],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[483],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::select_bool,
   },
   {
     /* [267] */
-    /* num parameters */ 1,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[953],
-    /* return matcher indices */ &kMatcherIndices[9],
-    /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::Identity,
+    /* num parameters */ 3,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[27],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[486],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::select_boolvec,
   },
   {
     /* [268] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[31],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[954],
-    /* return matcher indices */ &kMatcherIndices[9],
-    /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::Conv,
-  },
-  {
-    /* [269] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
-    /* return matcher indices */ &kMatcherIndices[105],
+    /* parameters */ &kParameters[1014],
+    /* return matcher indices */ &kMatcherIndices[9],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
   },
   {
-    /* [270] */
+    /* [269] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[955],
-    /* return matcher indices */ &kMatcherIndices[105],
+    /* return matcher indices */ &kMatcherIndices[9],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
   },
   {
-    /* [271] */
+    /* [270] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[32],
+    /* template types */ &kTemplateTypes[31],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[956],
-    /* return matcher indices */ &kMatcherIndices[105],
+    /* return matcher indices */ &kMatcherIndices[9],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
   },
   {
-    /* [272] */
+    /* [271] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
-    /* return matcher indices */ &kMatcherIndices[42],
+    /* parameters */ &kParameters[1014],
+    /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
   },
   {
-    /* [273] */
+    /* [272] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[957],
-    /* return matcher indices */ &kMatcherIndices[42],
+    /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
   },
   {
-    /* [274] */
+    /* [273] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[33],
+    /* template types */ &kTemplateTypes[32],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[958],
-    /* return matcher indices */ &kMatcherIndices[42],
+    /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
   },
   {
-    /* [275] */
+    /* [274] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
-    /* return matcher indices */ &kMatcherIndices[1],
+    /* parameters */ &kParameters[1014],
+    /* return matcher indices */ &kMatcherIndices[42],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
   },
   {
-    /* [276] */
+    /* [275] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[959],
-    /* return matcher indices */ &kMatcherIndices[1],
+    /* return matcher indices */ &kMatcherIndices[42],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
   },
   {
-    /* [277] */
+    /* [276] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[34],
+    /* template types */ &kTemplateTypes[33],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[960],
-    /* return matcher indices */ &kMatcherIndices[1],
+    /* return matcher indices */ &kMatcherIndices[42],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
   },
   {
-    /* [278] */
+    /* [277] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
-    /* return matcher indices */ &kMatcherIndices[39],
+    /* parameters */ &kParameters[1014],
+    /* return matcher indices */ &kMatcherIndices[1],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Zero,
   },
   {
-    /* [279] */
+    /* [278] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[961],
+    /* return matcher indices */ &kMatcherIndices[1],
+    /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::Identity,
+  },
+  {
+    /* [279] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[34],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[962],
+    /* return matcher indices */ &kMatcherIndices[1],
+    /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::Conv,
+  },
+  {
+    /* [280] */
+    /* num parameters */ 0,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[38],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[1014],
+    /* return matcher indices */ &kMatcherIndices[39],
+    /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::Zero,
+  },
+  {
+    /* [281] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[38],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[963],
     /* return matcher indices */ &kMatcherIndices[39],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Identity,
   },
   {
-    /* [280] */
+    /* [282] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[35],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[962],
+    /* parameters */ &kParameters[964],
     /* return matcher indices */ &kMatcherIndices[39],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
   },
   {
-    /* [281] */
+    /* [283] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11784,7 +11818,7 @@
     /* const eval */ &ConstEval::abs,
   },
   {
-    /* [282] */
+    /* [284] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11796,7 +11830,7 @@
     /* const eval */ &ConstEval::abs,
   },
   {
-    /* [283] */
+    /* [285] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11808,7 +11842,7 @@
     /* const eval */ &ConstEval::acos,
   },
   {
-    /* [284] */
+    /* [286] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11820,7 +11854,7 @@
     /* const eval */ &ConstEval::acos,
   },
   {
-    /* [285] */
+    /* [287] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11832,7 +11866,7 @@
     /* const eval */ &ConstEval::acosh,
   },
   {
-    /* [286] */
+    /* [288] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11844,7 +11878,7 @@
     /* const eval */ &ConstEval::acosh,
   },
   {
-    /* [287] */
+    /* [289] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -11856,7 +11890,7 @@
     /* const eval */ &ConstEval::all,
   },
   {
-    /* [288] */
+    /* [290] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -11868,7 +11902,7 @@
     /* const eval */ &ConstEval::all,
   },
   {
-    /* [289] */
+    /* [291] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -11880,7 +11914,7 @@
     /* const eval */ &ConstEval::any,
   },
   {
-    /* [290] */
+    /* [292] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -11892,7 +11926,7 @@
     /* const eval */ &ConstEval::any,
   },
   {
-    /* [291] */
+    /* [293] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11904,7 +11938,7 @@
     /* const eval */ &ConstEval::asin,
   },
   {
-    /* [292] */
+    /* [294] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11916,7 +11950,7 @@
     /* const eval */ &ConstEval::asin,
   },
   {
-    /* [293] */
+    /* [295] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11928,7 +11962,7 @@
     /* const eval */ &ConstEval::asinh,
   },
   {
-    /* [294] */
+    /* [296] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11940,7 +11974,7 @@
     /* const eval */ &ConstEval::asinh,
   },
   {
-    /* [295] */
+    /* [297] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11952,7 +11986,7 @@
     /* const eval */ &ConstEval::atan,
   },
   {
-    /* [296] */
+    /* [298] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11964,7 +11998,7 @@
     /* const eval */ &ConstEval::atan,
   },
   {
-    /* [297] */
+    /* [299] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11976,7 +12010,7 @@
     /* const eval */ &ConstEval::atan2,
   },
   {
-    /* [298] */
+    /* [300] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11988,7 +12022,7 @@
     /* const eval */ &ConstEval::atan2,
   },
   {
-    /* [299] */
+    /* [301] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12000,7 +12034,7 @@
     /* const eval */ &ConstEval::atanh,
   },
   {
-    /* [300] */
+    /* [302] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12012,7 +12046,7 @@
     /* const eval */ &ConstEval::atanh,
   },
   {
-    /* [301] */
+    /* [303] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12024,7 +12058,7 @@
     /* const eval */ &ConstEval::ceil,
   },
   {
-    /* [302] */
+    /* [304] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12036,7 +12070,7 @@
     /* const eval */ &ConstEval::ceil,
   },
   {
-    /* [303] */
+    /* [305] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12048,7 +12082,7 @@
     /* const eval */ &ConstEval::clamp,
   },
   {
-    /* [304] */
+    /* [306] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12060,7 +12094,7 @@
     /* const eval */ &ConstEval::clamp,
   },
   {
-    /* [305] */
+    /* [307] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12072,7 +12106,7 @@
     /* const eval */ &ConstEval::cos,
   },
   {
-    /* [306] */
+    /* [308] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12084,7 +12118,7 @@
     /* const eval */ &ConstEval::cos,
   },
   {
-    /* [307] */
+    /* [309] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12096,7 +12130,7 @@
     /* const eval */ &ConstEval::cosh,
   },
   {
-    /* [308] */
+    /* [310] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12108,7 +12142,7 @@
     /* const eval */ &ConstEval::cosh,
   },
   {
-    /* [309] */
+    /* [311] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12120,7 +12154,7 @@
     /* const eval */ &ConstEval::countLeadingZeros,
   },
   {
-    /* [310] */
+    /* [312] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12132,7 +12166,7 @@
     /* const eval */ &ConstEval::countLeadingZeros,
   },
   {
-    /* [311] */
+    /* [313] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12144,7 +12178,7 @@
     /* const eval */ &ConstEval::countOneBits,
   },
   {
-    /* [312] */
+    /* [314] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12156,7 +12190,7 @@
     /* const eval */ &ConstEval::countOneBits,
   },
   {
-    /* [313] */
+    /* [315] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12168,7 +12202,7 @@
     /* const eval */ &ConstEval::countTrailingZeros,
   },
   {
-    /* [314] */
+    /* [316] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12180,7 +12214,7 @@
     /* const eval */ &ConstEval::countTrailingZeros,
   },
   {
-    /* [315] */
+    /* [317] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12192,7 +12226,7 @@
     /* const eval */ &ConstEval::degrees,
   },
   {
-    /* [316] */
+    /* [318] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12204,7 +12238,7 @@
     /* const eval */ &ConstEval::degrees,
   },
   {
-    /* [317] */
+    /* [319] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12216,7 +12250,7 @@
     /* const eval */ &ConstEval::distance,
   },
   {
-    /* [318] */
+    /* [320] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12228,7 +12262,7 @@
     /* const eval */ &ConstEval::distance,
   },
   {
-    /* [319] */
+    /* [321] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -12240,7 +12274,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [320] */
+    /* [322] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -12252,7 +12286,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [321] */
+    /* [323] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -12264,7 +12298,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [322] */
+    /* [324] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -12276,7 +12310,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [323] */
+    /* [325] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -12288,7 +12322,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [324] */
+    /* [326] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -12300,7 +12334,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [325] */
+    /* [327] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -12312,7 +12346,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [326] */
+    /* [328] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -12324,7 +12358,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [327] */
+    /* [329] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -12336,7 +12370,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [328] */
+    /* [330] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -12348,7 +12382,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [329] */
+    /* [331] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -12360,7 +12394,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [330] */
+    /* [332] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -12372,7 +12406,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [331] */
+    /* [333] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12384,7 +12418,7 @@
     /* const eval */ &ConstEval::exp,
   },
   {
-    /* [332] */
+    /* [334] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12396,7 +12430,7 @@
     /* const eval */ &ConstEval::exp,
   },
   {
-    /* [333] */
+    /* [335] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12408,7 +12442,7 @@
     /* const eval */ &ConstEval::exp2,
   },
   {
-    /* [334] */
+    /* [336] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12420,7 +12454,7 @@
     /* const eval */ &ConstEval::exp2,
   },
   {
-    /* [335] */
+    /* [337] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12432,7 +12466,7 @@
     /* const eval */ &ConstEval::extractBits,
   },
   {
-    /* [336] */
+    /* [338] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12444,7 +12478,7 @@
     /* const eval */ &ConstEval::extractBits,
   },
   {
-    /* [337] */
+    /* [339] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12456,7 +12490,7 @@
     /* const eval */ &ConstEval::firstLeadingBit,
   },
   {
-    /* [338] */
+    /* [340] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12468,7 +12502,7 @@
     /* const eval */ &ConstEval::firstLeadingBit,
   },
   {
-    /* [339] */
+    /* [341] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12480,7 +12514,7 @@
     /* const eval */ &ConstEval::firstTrailingBit,
   },
   {
-    /* [340] */
+    /* [342] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12492,7 +12526,7 @@
     /* const eval */ &ConstEval::firstTrailingBit,
   },
   {
-    /* [341] */
+    /* [343] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12504,7 +12538,7 @@
     /* const eval */ &ConstEval::floor,
   },
   {
-    /* [342] */
+    /* [344] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12516,7 +12550,7 @@
     /* const eval */ &ConstEval::floor,
   },
   {
-    /* [343] */
+    /* [345] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12528,7 +12562,7 @@
     /* const eval */ &ConstEval::fma,
   },
   {
-    /* [344] */
+    /* [346] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12540,7 +12574,7 @@
     /* const eval */ &ConstEval::fma,
   },
   {
-    /* [345] */
+    /* [347] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12552,7 +12586,7 @@
     /* const eval */ &ConstEval::fract,
   },
   {
-    /* [346] */
+    /* [348] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12564,7 +12598,7 @@
     /* const eval */ &ConstEval::fract,
   },
   {
-    /* [347] */
+    /* [349] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12576,7 +12610,7 @@
     /* const eval */ &ConstEval::frexp,
   },
   {
-    /* [348] */
+    /* [350] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12588,7 +12622,7 @@
     /* const eval */ &ConstEval::frexp,
   },
   {
-    /* [349] */
+    /* [351] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -12600,7 +12634,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [350] */
+    /* [352] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -12612,7 +12646,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [351] */
+    /* [353] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -12624,7 +12658,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [352] */
+    /* [354] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -12636,7 +12670,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [353] */
+    /* [355] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -12648,7 +12682,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [354] */
+    /* [356] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -12660,7 +12694,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [355] */
+    /* [357] */
     /* num parameters */ 4,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12672,7 +12706,7 @@
     /* const eval */ &ConstEval::insertBits,
   },
   {
-    /* [356] */
+    /* [358] */
     /* num parameters */ 4,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12684,7 +12718,7 @@
     /* const eval */ &ConstEval::insertBits,
   },
   {
-    /* [357] */
+    /* [359] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12696,7 +12730,7 @@
     /* const eval */ &ConstEval::inverseSqrt,
   },
   {
-    /* [358] */
+    /* [360] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12708,7 +12742,7 @@
     /* const eval */ &ConstEval::inverseSqrt,
   },
   {
-    /* [359] */
+    /* [361] */
     /* num parameters */ 2,
     /* num template types */ 2,
     /* num template numbers */ 0,
@@ -12720,7 +12754,7 @@
     /* const eval */ &ConstEval::ldexp,
   },
   {
-    /* [360] */
+    /* [362] */
     /* num parameters */ 2,
     /* num template types */ 2,
     /* num template numbers */ 1,
@@ -12732,7 +12766,7 @@
     /* const eval */ &ConstEval::ldexp,
   },
   {
-    /* [361] */
+    /* [363] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12744,7 +12778,7 @@
     /* const eval */ &ConstEval::length,
   },
   {
-    /* [362] */
+    /* [364] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12756,7 +12790,7 @@
     /* const eval */ &ConstEval::length,
   },
   {
-    /* [363] */
+    /* [365] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12768,7 +12802,7 @@
     /* const eval */ &ConstEval::log,
   },
   {
-    /* [364] */
+    /* [366] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12780,7 +12814,7 @@
     /* const eval */ &ConstEval::log,
   },
   {
-    /* [365] */
+    /* [367] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12792,7 +12826,7 @@
     /* const eval */ &ConstEval::log2,
   },
   {
-    /* [366] */
+    /* [368] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12804,7 +12838,7 @@
     /* const eval */ &ConstEval::log2,
   },
   {
-    /* [367] */
+    /* [369] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12816,7 +12850,7 @@
     /* const eval */ &ConstEval::max,
   },
   {
-    /* [368] */
+    /* [370] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12828,7 +12862,7 @@
     /* const eval */ &ConstEval::max,
   },
   {
-    /* [369] */
+    /* [371] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12840,7 +12874,7 @@
     /* const eval */ &ConstEval::min,
   },
   {
-    /* [370] */
+    /* [372] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12852,7 +12886,7 @@
     /* const eval */ &ConstEval::min,
   },
   {
-    /* [371] */
+    /* [373] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12864,7 +12898,7 @@
     /* const eval */ &ConstEval::modf,
   },
   {
-    /* [372] */
+    /* [374] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12876,7 +12910,7 @@
     /* const eval */ &ConstEval::modf,
   },
   {
-    /* [373] */
+    /* [375] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12888,7 +12922,7 @@
     /* const eval */ &ConstEval::pow,
   },
   {
-    /* [374] */
+    /* [376] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12900,7 +12934,7 @@
     /* const eval */ &ConstEval::pow,
   },
   {
-    /* [375] */
+    /* [377] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -12912,7 +12946,7 @@
     /* const eval */ &ConstEval::quantizeToF16,
   },
   {
-    /* [376] */
+    /* [378] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
@@ -12924,7 +12958,7 @@
     /* const eval */ &ConstEval::quantizeToF16,
   },
   {
-    /* [377] */
+    /* [379] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12936,7 +12970,7 @@
     /* const eval */ &ConstEval::radians,
   },
   {
-    /* [378] */
+    /* [380] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12948,7 +12982,7 @@
     /* const eval */ &ConstEval::radians,
   },
   {
-    /* [379] */
+    /* [381] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12960,7 +12994,7 @@
     /* const eval */ &ConstEval::reverseBits,
   },
   {
-    /* [380] */
+    /* [382] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12972,7 +13006,7 @@
     /* const eval */ &ConstEval::reverseBits,
   },
   {
-    /* [381] */
+    /* [383] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12984,7 +13018,7 @@
     /* const eval */ &ConstEval::round,
   },
   {
-    /* [382] */
+    /* [384] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12996,7 +13030,7 @@
     /* const eval */ &ConstEval::round,
   },
   {
-    /* [383] */
+    /* [385] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13008,7 +13042,7 @@
     /* const eval */ &ConstEval::saturate,
   },
   {
-    /* [384] */
+    /* [386] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13020,7 +13054,7 @@
     /* const eval */ &ConstEval::saturate,
   },
   {
-    /* [385] */
+    /* [387] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13032,7 +13066,7 @@
     /* const eval */ &ConstEval::sign,
   },
   {
-    /* [386] */
+    /* [388] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13044,7 +13078,7 @@
     /* const eval */ &ConstEval::sign,
   },
   {
-    /* [387] */
+    /* [389] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13056,7 +13090,7 @@
     /* const eval */ &ConstEval::sin,
   },
   {
-    /* [388] */
+    /* [390] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13068,7 +13102,7 @@
     /* const eval */ &ConstEval::sin,
   },
   {
-    /* [389] */
+    /* [391] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13080,7 +13114,7 @@
     /* const eval */ &ConstEval::sinh,
   },
   {
-    /* [390] */
+    /* [392] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13092,7 +13126,7 @@
     /* const eval */ &ConstEval::sinh,
   },
   {
-    /* [391] */
+    /* [393] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13104,7 +13138,7 @@
     /* const eval */ &ConstEval::smoothstep,
   },
   {
-    /* [392] */
+    /* [394] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13116,7 +13150,7 @@
     /* const eval */ &ConstEval::smoothstep,
   },
   {
-    /* [393] */
+    /* [395] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13128,7 +13162,7 @@
     /* const eval */ &ConstEval::sqrt,
   },
   {
-    /* [394] */
+    /* [396] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13140,7 +13174,7 @@
     /* const eval */ &ConstEval::sqrt,
   },
   {
-    /* [395] */
+    /* [397] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13152,7 +13186,7 @@
     /* const eval */ &ConstEval::step,
   },
   {
-    /* [396] */
+    /* [398] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13164,7 +13198,7 @@
     /* const eval */ &ConstEval::step,
   },
   {
-    /* [397] */
+    /* [399] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13176,7 +13210,7 @@
     /* const eval */ &ConstEval::tan,
   },
   {
-    /* [398] */
+    /* [400] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13188,7 +13222,7 @@
     /* const eval */ &ConstEval::tan,
   },
   {
-    /* [399] */
+    /* [401] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13200,7 +13234,7 @@
     /* const eval */ &ConstEval::tanh,
   },
   {
-    /* [400] */
+    /* [402] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13212,7 +13246,7 @@
     /* const eval */ &ConstEval::tanh,
   },
   {
-    /* [401] */
+    /* [403] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13224,7 +13258,7 @@
     /* const eval */ &ConstEval::trunc,
   },
   {
-    /* [402] */
+    /* [404] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13236,31 +13270,31 @@
     /* const eval */ &ConstEval::trunc,
   },
   {
-    /* [403] */
+    /* [405] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[943],
+    /* parameters */ &kParameters[945],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
   },
   {
-    /* [404] */
+    /* [406] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[944],
+    /* parameters */ &kParameters[946],
     /* return matcher indices */ &kMatcherIndices[105],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ nullptr,
   },
   {
-    /* [405] */
+    /* [407] */
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13272,7 +13306,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [406] */
+    /* [408] */
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13284,79 +13318,79 @@
     /* const eval */ nullptr,
   },
   {
-    /* [407] */
+    /* [409] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[947],
+    /* parameters */ &kParameters[949],
     /* return matcher indices */ &kMatcherIndices[39],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::OpNot,
   },
   {
-    /* [408] */
+    /* [410] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[948],
+    /* parameters */ &kParameters[950],
     /* return matcher indices */ &kMatcherIndices[37],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::OpNot,
   },
   {
-    /* [409] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[29],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[949],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpComplement,
-  },
-  {
-    /* [410] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[29],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[950],
-    /* return matcher indices */ &kMatcherIndices[34],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpComplement,
-  },
-  {
     /* [411] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[951],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
-    /* const eval */ &ConstEval::OpUnaryMinus,
+    /* const eval */ &ConstEval::OpComplement,
   },
   {
     /* [412] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[28],
+    /* template types */ &kTemplateTypes[29],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[952],
     /* return matcher indices */ &kMatcherIndices[34],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpComplement,
+  },
+  {
+    /* [413] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[28],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[953],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
+    /* const eval */ &ConstEval::OpUnaryMinus,
+  },
+  {
+    /* [414] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[28],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[954],
+    /* return matcher indices */ &kMatcherIndices[34],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::OpUnaryMinus,
   },
   {
-    /* [413] */
+    /* [415] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13368,7 +13402,7 @@
     /* const eval */ &ConstEval::OpXor,
   },
   {
-    /* [414] */
+    /* [416] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13380,7 +13414,7 @@
     /* const eval */ &ConstEval::OpXor,
   },
   {
-    /* [415] */
+    /* [417] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13392,7 +13426,7 @@
     /* const eval */ &ConstEval::OpEqual,
   },
   {
-    /* [416] */
+    /* [418] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13404,7 +13438,7 @@
     /* const eval */ &ConstEval::OpEqual,
   },
   {
-    /* [417] */
+    /* [419] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13416,7 +13450,7 @@
     /* const eval */ &ConstEval::OpNotEqual,
   },
   {
-    /* [418] */
+    /* [420] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13428,7 +13462,7 @@
     /* const eval */ &ConstEval::OpNotEqual,
   },
   {
-    /* [419] */
+    /* [421] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13440,7 +13474,7 @@
     /* const eval */ &ConstEval::OpLessThan,
   },
   {
-    /* [420] */
+    /* [422] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13452,7 +13486,7 @@
     /* const eval */ &ConstEval::OpLessThan,
   },
   {
-    /* [421] */
+    /* [423] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13464,7 +13498,7 @@
     /* const eval */ &ConstEval::OpGreaterThan,
   },
   {
-    /* [422] */
+    /* [424] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13476,7 +13510,7 @@
     /* const eval */ &ConstEval::OpGreaterThan,
   },
   {
-    /* [423] */
+    /* [425] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13488,7 +13522,7 @@
     /* const eval */ &ConstEval::OpLessThanEqual,
   },
   {
-    /* [424] */
+    /* [426] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13500,7 +13534,7 @@
     /* const eval */ &ConstEval::OpLessThanEqual,
   },
   {
-    /* [425] */
+    /* [427] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13512,7 +13546,7 @@
     /* const eval */ &ConstEval::OpGreaterThanEqual,
   },
   {
-    /* [426] */
+    /* [428] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13524,7 +13558,7 @@
     /* const eval */ &ConstEval::OpGreaterThanEqual,
   },
   {
-    /* [427] */
+    /* [429] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13536,7 +13570,7 @@
     /* const eval */ &ConstEval::OpShiftLeft,
   },
   {
-    /* [428] */
+    /* [430] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13548,7 +13582,7 @@
     /* const eval */ &ConstEval::OpShiftLeft,
   },
   {
-    /* [429] */
+    /* [431] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13560,7 +13594,7 @@
     /* const eval */ &ConstEval::OpShiftRight,
   },
   {
-    /* [430] */
+    /* [432] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13572,7 +13606,7 @@
     /* const eval */ &ConstEval::OpShiftRight,
   },
   {
-    /* [431] */
+    /* [433] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13584,7 +13618,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [432] */
+    /* [434] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13596,7 +13630,7 @@
     /* const eval */ &ConstEval::cross,
   },
   {
-    /* [433] */
+    /* [435] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13608,7 +13642,7 @@
     /* const eval */ &ConstEval::determinant,
   },
   {
-    /* [434] */
+    /* [436] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13620,7 +13654,7 @@
     /* const eval */ &ConstEval::dot,
   },
   {
-    /* [435] */
+    /* [437] */
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13632,7 +13666,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [436] */
+    /* [438] */
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13644,7 +13678,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [437] */
+    /* [439] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13656,7 +13690,7 @@
     /* const eval */ &ConstEval::faceForward,
   },
   {
-    /* [438] */
+    /* [440] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13668,7 +13702,7 @@
     /* const eval */ &ConstEval::normalize,
   },
   {
-    /* [439] */
+    /* [441] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13680,7 +13714,7 @@
     /* const eval */ &ConstEval::pack2x16float,
   },
   {
-    /* [440] */
+    /* [442] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13692,7 +13726,7 @@
     /* const eval */ &ConstEval::pack2x16snorm,
   },
   {
-    /* [441] */
+    /* [443] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13704,7 +13738,7 @@
     /* const eval */ &ConstEval::pack2x16unorm,
   },
   {
-    /* [442] */
+    /* [444] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13716,7 +13750,7 @@
     /* const eval */ &ConstEval::pack4x8snorm,
   },
   {
-    /* [443] */
+    /* [445] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13728,7 +13762,7 @@
     /* const eval */ &ConstEval::pack4x8unorm,
   },
   {
-    /* [444] */
+    /* [446] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13740,7 +13774,7 @@
     /* const eval */ &ConstEval::reflect,
   },
   {
-    /* [445] */
+    /* [447] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13752,19 +13786,19 @@
     /* const eval */ &ConstEval::refract,
   },
   {
-    /* [446] */
+    /* [448] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ nullptr,
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
-    /* [447] */
+    /* [449] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 2,
@@ -13776,7 +13810,7 @@
     /* const eval */ &ConstEval::transpose,
   },
   {
-    /* [448] */
+    /* [450] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13788,7 +13822,7 @@
     /* const eval */ &ConstEval::unpack2x16float,
   },
   {
-    /* [449] */
+    /* [451] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13800,7 +13834,7 @@
     /* const eval */ &ConstEval::unpack2x16snorm,
   },
   {
-    /* [450] */
+    /* [452] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13812,7 +13846,7 @@
     /* const eval */ &ConstEval::unpack2x16unorm,
   },
   {
-    /* [451] */
+    /* [453] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13824,7 +13858,7 @@
     /* const eval */ &ConstEval::unpack4x8snorm,
   },
   {
-    /* [452] */
+    /* [454] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -13836,19 +13870,19 @@
     /* const eval */ &ConstEval::unpack4x8unorm,
   },
   {
-    /* [453] */
+    /* [455] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[38],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1014],
     /* return matcher indices */ nullptr,
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
-    /* [454] */
+    /* [456] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13860,37 +13894,13 @@
     /* const eval */ nullptr,
   },
   {
-    /* [455] */
+    /* [457] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[945],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
-  },
-  {
-    /* [456] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[26],
-    /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[654],
-    /* return matcher indices */ nullptr,
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
-  },
-  {
-    /* [457] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[26],
-    /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[656],
+    /* parameters */ &kParameters[947],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -13902,8 +13912,8 @@
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[658],
-    /* return matcher indices */ &kMatcherIndices[3],
+    /* parameters */ &kParameters[654],
+    /* return matcher indices */ nullptr,
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
@@ -13914,7 +13924,7 @@
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[660],
+    /* parameters */ &kParameters[656],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -13926,7 +13936,7 @@
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[662],
+    /* parameters */ &kParameters[658],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -13938,7 +13948,7 @@
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[664],
+    /* parameters */ &kParameters[660],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -13950,7 +13960,7 @@
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[666],
+    /* parameters */ &kParameters[662],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -13962,7 +13972,7 @@
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[668],
+    /* parameters */ &kParameters[664],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -13974,13 +13984,37 @@
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[670],
+    /* parameters */ &kParameters[666],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
     /* [465] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[26],
+    /* template numbers */ &kTemplateNumbers[9],
+    /* parameters */ &kParameters[668],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ nullptr,
+  },
+  {
+    /* [466] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[26],
+    /* template numbers */ &kTemplateNumbers[9],
+    /* parameters */ &kParameters[670],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ nullptr,
+  },
+  {
+    /* [467] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13992,19 +14026,19 @@
     /* const eval */ nullptr,
   },
   {
-    /* [466] */
+    /* [468] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[25],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[946],
+    /* parameters */ &kParameters[948],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
   {
-    /* [467] */
+    /* [469] */
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -14016,7 +14050,7 @@
     /* const eval */ &ConstEval::OpLogicalAnd,
   },
   {
-    /* [468] */
+    /* [470] */
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 0,
@@ -14028,13 +14062,13 @@
     /* const eval */ &ConstEval::OpLogicalOr,
   },
   {
-    /* [469] */
+    /* [471] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[36],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1011],
+    /* parameters */ &kParameters[1013],
     /* return matcher indices */ &kMatcherIndices[232],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline, OverloadFlag::kMustUse),
     /* const eval */ &ConstEval::Conv,
@@ -14047,357 +14081,357 @@
     /* fn abs<T : fia_fiu32_f16>(T) -> T */
     /* fn abs<N : num, T : fia_fiu32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[281],
+    /* overloads */ &kOverloads[283],
   },
   {
     /* [1] */
     /* fn acos<T : fa_f32_f16>(@test_value(0.96891242171) T) -> T */
     /* fn acos<N : num, T : fa_f32_f16>(@test_value(0.96891242171) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[283],
+    /* overloads */ &kOverloads[285],
   },
   {
     /* [2] */
     /* fn acosh<T : fa_f32_f16>(@test_value(1.5430806348) T) -> T */
     /* fn acosh<N : num, T : fa_f32_f16>(@test_value(1.5430806348) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[285],
+    /* overloads */ &kOverloads[287],
   },
   {
     /* [3] */
     /* fn all(bool) -> bool */
     /* fn all<N : num>(vec<N, bool>) -> bool */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[287],
+    /* overloads */ &kOverloads[289],
   },
   {
     /* [4] */
     /* fn any(bool) -> bool */
     /* fn any<N : num>(vec<N, bool>) -> bool */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[289],
+    /* overloads */ &kOverloads[291],
   },
   {
     /* [5] */
     /* fn arrayLength<T, A : access>(ptr<storage, array<T>, A>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[431],
+    /* overloads */ &kOverloads[433],
   },
   {
     /* [6] */
     /* fn asin<T : fa_f32_f16>(@test_value(0.479425538604) T) -> T */
     /* fn asin<N : num, T : fa_f32_f16>(@test_value(0.479425538604) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[291],
+    /* overloads */ &kOverloads[293],
   },
   {
     /* [7] */
     /* fn asinh<T : fa_f32_f16>(T) -> T */
     /* fn asinh<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[293],
+    /* overloads */ &kOverloads[295],
   },
   {
     /* [8] */
     /* fn atan<T : fa_f32_f16>(T) -> T */
     /* fn atan<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[295],
+    /* overloads */ &kOverloads[297],
   },
   {
     /* [9] */
     /* fn atan2<T : fa_f32_f16>(T, T) -> T */
     /* fn atan2<T : fa_f32_f16, N : num>(vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[297],
+    /* overloads */ &kOverloads[299],
   },
   {
     /* [10] */
     /* fn atanh<T : fa_f32_f16>(@test_value(0.5) T) -> T */
     /* fn atanh<N : num, T : fa_f32_f16>(@test_value(0.5) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[299],
+    /* overloads */ &kOverloads[301],
   },
   {
     /* [11] */
     /* fn ceil<T : fa_f32_f16>(@test_value(1.5) T) -> T */
     /* fn ceil<N : num, T : fa_f32_f16>(@test_value(1.5) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[301],
+    /* overloads */ &kOverloads[303],
   },
   {
     /* [12] */
     /* fn clamp<T : fia_fiu32_f16>(T, T, T) -> T */
     /* fn clamp<T : fia_fiu32_f16, N : num>(vec<N, T>, vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[303],
+    /* overloads */ &kOverloads[305],
   },
   {
     /* [13] */
     /* fn cos<T : fa_f32_f16>(@test_value(0) T) -> T */
     /* fn cos<N : num, T : fa_f32_f16>(@test_value(0) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[305],
+    /* overloads */ &kOverloads[307],
   },
   {
     /* [14] */
     /* fn cosh<T : fa_f32_f16>(@test_value(0) T) -> T */
     /* fn cosh<N : num, T : fa_f32_f16>(@test_value(0) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[307],
+    /* overloads */ &kOverloads[309],
   },
   {
     /* [15] */
     /* fn countLeadingZeros<T : iu32>(T) -> T */
     /* fn countLeadingZeros<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[309],
+    /* overloads */ &kOverloads[311],
   },
   {
     /* [16] */
     /* fn countOneBits<T : iu32>(T) -> T */
     /* fn countOneBits<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[311],
+    /* overloads */ &kOverloads[313],
   },
   {
     /* [17] */
     /* fn countTrailingZeros<T : iu32>(T) -> T */
     /* fn countTrailingZeros<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[313],
+    /* overloads */ &kOverloads[315],
   },
   {
     /* [18] */
     /* fn cross<T : fa_f32_f16>(vec3<T>, vec3<T>) -> vec3<T> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[432],
+    /* overloads */ &kOverloads[434],
   },
   {
     /* [19] */
     /* fn degrees<T : fa_f32_f16>(T) -> T */
     /* fn degrees<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[315],
+    /* overloads */ &kOverloads[317],
   },
   {
     /* [20] */
     /* fn determinant<N : num, T : fa_f32_f16>(mat<N, N, T>) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[433],
+    /* overloads */ &kOverloads[435],
   },
   {
     /* [21] */
     /* fn distance<T : fa_f32_f16>(T, T) -> T */
     /* fn distance<N : num, T : fa_f32_f16>(vec<N, T>, vec<N, T>) -> T */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[317],
+    /* overloads */ &kOverloads[319],
   },
   {
     /* [22] */
     /* fn dot<N : num, T : fia_fiu32_f16>(vec<N, T>, vec<N, T>) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[434],
+    /* overloads */ &kOverloads[436],
   },
   {
     /* [23] */
     /* fn dot4I8Packed(u32, u32) -> i32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[435],
+    /* overloads */ &kOverloads[437],
   },
   {
     /* [24] */
     /* fn dot4U8Packed(u32, u32) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[436],
+    /* overloads */ &kOverloads[438],
   },
   {
     /* [25] */
     /* fn dpdx(f32) -> f32 */
     /* fn dpdx<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[319],
+    /* overloads */ &kOverloads[321],
   },
   {
     /* [26] */
     /* fn dpdxCoarse(f32) -> f32 */
     /* fn dpdxCoarse<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[321],
+    /* overloads */ &kOverloads[323],
   },
   {
     /* [27] */
     /* fn dpdxFine(f32) -> f32 */
     /* fn dpdxFine<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[323],
+    /* overloads */ &kOverloads[325],
   },
   {
     /* [28] */
     /* fn dpdy(f32) -> f32 */
     /* fn dpdy<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[325],
+    /* overloads */ &kOverloads[327],
   },
   {
     /* [29] */
     /* fn dpdyCoarse(f32) -> f32 */
     /* fn dpdyCoarse<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[327],
+    /* overloads */ &kOverloads[329],
   },
   {
     /* [30] */
     /* fn dpdyFine(f32) -> f32 */
     /* fn dpdyFine<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[329],
+    /* overloads */ &kOverloads[331],
   },
   {
     /* [31] */
     /* fn exp<T : fa_f32_f16>(T) -> T */
     /* fn exp<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[331],
+    /* overloads */ &kOverloads[333],
   },
   {
     /* [32] */
     /* fn exp2<T : fa_f32_f16>(T) -> T */
     /* fn exp2<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[333],
+    /* overloads */ &kOverloads[335],
   },
   {
     /* [33] */
     /* fn extractBits<T : iu32>(T, u32, u32) -> T */
     /* fn extractBits<N : num, T : iu32>(vec<N, T>, u32, u32) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[335],
+    /* overloads */ &kOverloads[337],
   },
   {
     /* [34] */
     /* fn faceForward<N : num, T : fa_f32_f16>(vec<N, T>, vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[437],
+    /* overloads */ &kOverloads[439],
   },
   {
     /* [35] */
     /* fn firstLeadingBit<T : iu32>(T) -> T */
     /* fn firstLeadingBit<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[337],
+    /* overloads */ &kOverloads[339],
   },
   {
     /* [36] */
     /* fn firstTrailingBit<T : iu32>(T) -> T */
     /* fn firstTrailingBit<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[339],
+    /* overloads */ &kOverloads[341],
   },
   {
     /* [37] */
     /* fn floor<T : fa_f32_f16>(@test_value(1.5) T) -> T */
     /* fn floor<N : num, T : fa_f32_f16>(@test_value(1.5) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[341],
+    /* overloads */ &kOverloads[343],
   },
   {
     /* [38] */
     /* fn fma<T : fa_f32_f16>(T, T, T) -> T */
     /* fn fma<N : num, T : fa_f32_f16>(vec<N, T>, vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[343],
+    /* overloads */ &kOverloads[345],
   },
   {
     /* [39] */
     /* fn fract<T : fa_f32_f16>(@test_value(1.25) T) -> T */
     /* fn fract<N : num, T : fa_f32_f16>(@test_value(1.25) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[345],
+    /* overloads */ &kOverloads[347],
   },
   {
     /* [40] */
     /* fn frexp<T : fa_f32_f16>(T) -> __frexp_result<T> */
     /* fn frexp<N : num, T : fa_f32_f16>(vec<N, T>) -> __frexp_result_vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[347],
+    /* overloads */ &kOverloads[349],
   },
   {
     /* [41] */
     /* fn fwidth(f32) -> f32 */
     /* fn fwidth<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[349],
+    /* overloads */ &kOverloads[351],
   },
   {
     /* [42] */
     /* fn fwidthCoarse(f32) -> f32 */
     /* fn fwidthCoarse<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[351],
+    /* overloads */ &kOverloads[353],
   },
   {
     /* [43] */
     /* fn fwidthFine(f32) -> f32 */
     /* fn fwidthFine<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[353],
+    /* overloads */ &kOverloads[355],
   },
   {
     /* [44] */
     /* fn insertBits<T : iu32>(T, T, u32, u32) -> T */
     /* fn insertBits<N : num, T : iu32>(vec<N, T>, vec<N, T>, u32, u32) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[355],
+    /* overloads */ &kOverloads[357],
   },
   {
     /* [45] */
     /* fn inverseSqrt<T : fa_f32_f16>(T) -> T */
     /* fn inverseSqrt<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[357],
+    /* overloads */ &kOverloads[359],
   },
   {
     /* [46] */
     /* fn ldexp<T : fa_f32_f16, U : ia_i32>(T, U) -> T */
     /* fn ldexp<N : num, T : fa_f32_f16, U : ia_i32>(vec<N, T>, vec<N, U>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[359],
+    /* overloads */ &kOverloads[361],
   },
   {
     /* [47] */
     /* fn length<T : fa_f32_f16>(@test_value(0) T) -> T */
     /* fn length<N : num, T : fa_f32_f16>(@test_value(0) vec<N, T>) -> T */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[361],
+    /* overloads */ &kOverloads[363],
   },
   {
     /* [48] */
     /* fn log<T : fa_f32_f16>(T) -> T */
     /* fn log<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[363],
+    /* overloads */ &kOverloads[365],
   },
   {
     /* [49] */
     /* fn log2<T : fa_f32_f16>(T) -> T */
     /* fn log2<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[365],
+    /* overloads */ &kOverloads[367],
   },
   {
     /* [50] */
     /* fn max<T : fia_fiu32_f16>(T, T) -> T */
     /* fn max<N : num, T : fia_fiu32_f16>(vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[367],
+    /* overloads */ &kOverloads[369],
   },
   {
     /* [51] */
     /* fn min<T : fia_fiu32_f16>(T, T) -> T */
     /* fn min<N : num, T : fia_fiu32_f16>(vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[369],
+    /* overloads */ &kOverloads[371],
   },
   {
     /* [52] */
@@ -14405,104 +14439,104 @@
     /* fn mix<N : num, T : fa_f32_f16>(vec<N, T>, vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* fn mix<N : num, T : fa_f32_f16>(vec<N, T>, vec<N, T>, T) -> vec<N, T> */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[257],
+    /* overloads */ &kOverloads[262],
   },
   {
     /* [53] */
     /* fn modf<T : fa_f32_f16>(@test_value(-1.5) T) -> __modf_result<T> */
     /* fn modf<N : num, T : fa_f32_f16>(@test_value(-1.5) vec<N, T>) -> __modf_result_vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[371],
+    /* overloads */ &kOverloads[373],
   },
   {
     /* [54] */
     /* fn normalize<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[438],
+    /* overloads */ &kOverloads[440],
   },
   {
     /* [55] */
     /* fn pack2x16float(vec2<f32>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[439],
+    /* overloads */ &kOverloads[441],
   },
   {
     /* [56] */
     /* fn pack2x16snorm(vec2<f32>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[440],
+    /* overloads */ &kOverloads[442],
   },
   {
     /* [57] */
     /* fn pack2x16unorm(vec2<f32>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[441],
+    /* overloads */ &kOverloads[443],
   },
   {
     /* [58] */
     /* fn pack4x8snorm(vec4<f32>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[442],
+    /* overloads */ &kOverloads[444],
   },
   {
     /* [59] */
     /* fn pack4x8unorm(vec4<f32>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[443],
+    /* overloads */ &kOverloads[445],
   },
   {
     /* [60] */
     /* fn pow<T : fa_f32_f16>(T, T) -> T */
     /* fn pow<N : num, T : fa_f32_f16>(vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[373],
+    /* overloads */ &kOverloads[375],
   },
   {
     /* [61] */
     /* fn quantizeToF16(f32) -> f32 */
     /* fn quantizeToF16<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[375],
+    /* overloads */ &kOverloads[377],
   },
   {
     /* [62] */
     /* fn radians<T : fa_f32_f16>(T) -> T */
     /* fn radians<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[377],
+    /* overloads */ &kOverloads[379],
   },
   {
     /* [63] */
     /* fn reflect<N : num, T : fa_f32_f16>(vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[444],
+    /* overloads */ &kOverloads[446],
   },
   {
     /* [64] */
     /* fn refract<N : num, T : fa_f32_f16>(vec<N, T>, vec<N, T>, T) -> vec<N, T> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[445],
+    /* overloads */ &kOverloads[447],
   },
   {
     /* [65] */
     /* fn reverseBits<T : iu32>(T) -> T */
     /* fn reverseBits<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[379],
+    /* overloads */ &kOverloads[381],
   },
   {
     /* [66] */
     /* fn round<T : fa_f32_f16>(@test_value(3.5) T) -> T */
     /* fn round<N : num, T : fa_f32_f16>(@test_value(3.5) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[381],
+    /* overloads */ &kOverloads[383],
   },
   {
     /* [67] */
     /* fn saturate<T : fa_f32_f16>(@test_value(2) T) -> T */
     /* fn saturate<T : fa_f32_f16, N : num>(@test_value(2) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[383],
+    /* overloads */ &kOverloads[385],
   },
   {
     /* [68] */
@@ -14510,124 +14544,124 @@
     /* fn select<T : scalar, N : num>(vec<N, T>, vec<N, T>, bool) -> vec<N, T> */
     /* fn select<N : num, T : scalar>(vec<N, T>, vec<N, T>, vec<N, bool>) -> vec<N, T> */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[260],
+    /* overloads */ &kOverloads[265],
   },
   {
     /* [69] */
     /* fn sign<T : fia_fi32_f16>(T) -> T */
     /* fn sign<N : num, T : fia_fi32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[385],
+    /* overloads */ &kOverloads[387],
   },
   {
     /* [70] */
     /* fn sin<T : fa_f32_f16>(@test_value(1.57079632679) T) -> T */
     /* fn sin<N : num, T : fa_f32_f16>(@test_value(1.57079632679) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[387],
+    /* overloads */ &kOverloads[389],
   },
   {
     /* [71] */
     /* fn sinh<T : fa_f32_f16>(T) -> T */
     /* fn sinh<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[389],
+    /* overloads */ &kOverloads[391],
   },
   {
     /* [72] */
     /* fn smoothstep<T : fa_f32_f16>(@test_value(2) T, @test_value(4) T, @test_value(3) T) -> T */
     /* fn smoothstep<N : num, T : fa_f32_f16>(@test_value(2) vec<N, T>, @test_value(4) vec<N, T>, @test_value(3) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[391],
+    /* overloads */ &kOverloads[393],
   },
   {
     /* [73] */
     /* fn sqrt<T : fa_f32_f16>(T) -> T */
     /* fn sqrt<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[393],
+    /* overloads */ &kOverloads[395],
   },
   {
     /* [74] */
     /* fn step<T : fa_f32_f16>(T, T) -> T */
     /* fn step<N : num, T : fa_f32_f16>(vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[395],
+    /* overloads */ &kOverloads[397],
   },
   {
     /* [75] */
     /* fn storageBarrier() */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[446],
+    /* overloads */ &kOverloads[448],
   },
   {
     /* [76] */
     /* fn tan<T : fa_f32_f16>(T) -> T */
     /* fn tan<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[397],
+    /* overloads */ &kOverloads[399],
   },
   {
     /* [77] */
     /* fn tanh<T : fa_f32_f16>(T) -> T */
     /* fn tanh<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[399],
+    /* overloads */ &kOverloads[401],
   },
   {
     /* [78] */
     /* fn transpose<M : num, N : num, T : fa_f32_f16>(mat<M, N, T>) -> mat<N, M, T> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[447],
+    /* overloads */ &kOverloads[449],
   },
   {
     /* [79] */
     /* fn trunc<T : fa_f32_f16>(@test_value(1.5) T) -> T */
     /* fn trunc<N : num, T : fa_f32_f16>(@test_value(1.5) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[401],
+    /* overloads */ &kOverloads[403],
   },
   {
     /* [80] */
     /* fn unpack2x16float(u32) -> vec2<f32> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[448],
+    /* overloads */ &kOverloads[450],
   },
   {
     /* [81] */
     /* fn unpack2x16snorm(u32) -> vec2<f32> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[449],
+    /* overloads */ &kOverloads[451],
   },
   {
     /* [82] */
     /* fn unpack2x16unorm(u32) -> vec2<f32> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[450],
+    /* overloads */ &kOverloads[452],
   },
   {
     /* [83] */
     /* fn unpack4x8snorm(u32) -> vec4<f32> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[451],
+    /* overloads */ &kOverloads[453],
   },
   {
     /* [84] */
     /* fn unpack4x8unorm(u32) -> vec4<f32> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[452],
+    /* overloads */ &kOverloads[454],
   },
   {
     /* [85] */
     /* fn workgroupBarrier() */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[453],
+    /* overloads */ &kOverloads[455],
   },
   {
     /* [86] */
     /* fn workgroupUniformLoad<T>(ptr<workgroup, T, read_write>) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[454],
+    /* overloads */ &kOverloads[456],
   },
   {
     /* [87] */
@@ -14692,10 +14726,12 @@
   {
     /* [90] */
     /* fn textureNumLayers<T : fiu32>(texture: texture_2d_array<T>) -> u32 */
+    /* fn textureNumLayers<T : fiu32>(texture: texture_cube_array<T>) -> u32 */
     /* fn textureNumLayers(texture: texture_depth_2d_array) -> u32 */
+    /* fn textureNumLayers(texture: texture_depth_cube_array) -> u32 */
     /* fn textureNumLayers<F : texel_format, A : write>(texture: texture_storage_2d_array<F, A>) -> u32 */
-    /* num overloads */ 3,
-    /* overloads */ &kOverloads[263],
+    /* num overloads */ 5,
+    /* overloads */ &kOverloads[231],
   },
   {
     /* [91] */
@@ -14717,7 +14753,7 @@
     /* fn textureNumSamples<T : fiu32>(texture: texture_multisampled_2d<T>) -> u32 */
     /* fn textureNumSamples(texture: texture_depth_multisampled_2d) -> u32 */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[403],
+    /* overloads */ &kOverloads[405],
   },
   {
     /* [93] */
@@ -14811,7 +14847,7 @@
     /* fn textureSampleBaseClampToEdge(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>) -> vec4<f32> */
     /* fn textureSampleBaseClampToEdge(texture: texture_external, sampler: sampler, coords: vec2<f32>) -> vec4<f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[405],
+    /* overloads */ &kOverloads[407],
   },
   {
     /* [100] */
@@ -14848,73 +14884,73 @@
     /* [102] */
     /* fn atomicLoad<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[455],
+    /* overloads */ &kOverloads[457],
   },
   {
     /* [103] */
     /* fn atomicStore<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[456],
+    /* overloads */ &kOverloads[458],
   },
   {
     /* [104] */
     /* fn atomicAdd<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[457],
+    /* overloads */ &kOverloads[459],
   },
   {
     /* [105] */
     /* fn atomicSub<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[458],
+    /* overloads */ &kOverloads[460],
   },
   {
     /* [106] */
     /* fn atomicMax<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[459],
+    /* overloads */ &kOverloads[461],
   },
   {
     /* [107] */
     /* fn atomicMin<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[460],
+    /* overloads */ &kOverloads[462],
   },
   {
     /* [108] */
     /* fn atomicAnd<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[461],
+    /* overloads */ &kOverloads[463],
   },
   {
     /* [109] */
     /* fn atomicOr<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[462],
+    /* overloads */ &kOverloads[464],
   },
   {
     /* [110] */
     /* fn atomicXor<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[463],
+    /* overloads */ &kOverloads[465],
   },
   {
     /* [111] */
     /* fn atomicExchange<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[464],
+    /* overloads */ &kOverloads[466],
   },
   {
     /* [112] */
     /* fn atomicCompareExchangeWeak<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T, T) -> __atomic_compare_exchange_result<T> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[465],
+    /* overloads */ &kOverloads[467],
   },
   {
     /* [113] */
     /* fn _tint_materialize<T>(T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[466],
+    /* overloads */ &kOverloads[468],
   },
 };
 
@@ -14924,21 +14960,21 @@
     /* op !(bool) -> bool */
     /* op !<N : num>(vec<N, bool>) -> vec<N, bool> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[407],
+    /* overloads */ &kOverloads[409],
   },
   {
     /* [1] */
     /* op ~<T : ia_iu32>(T) -> T */
     /* op ~<T : ia_iu32, N : num>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[409],
+    /* overloads */ &kOverloads[411],
   },
   {
     /* [2] */
     /* op -<T : fia_fi32_f16>(T) -> T */
     /* op -<T : fia_fi32_f16, N : num>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[411],
+    /* overloads */ &kOverloads[413],
   },
 };
 constexpr uint8_t kUnaryOperatorNot = 0;
@@ -14954,7 +14990,7 @@
     /* op +<T : fia_fiu32_f16, N : num>(T, vec<N, T>) -> vec<N, T> */
     /* op +<T : fa_f32_f16, N : num, M : num>(mat<N, M, T>, mat<N, M, T>) -> mat<N, M, T> */
     /* num overloads */ 5,
-    /* overloads */ &kOverloads[231],
+    /* overloads */ &kOverloads[236],
   },
   {
     /* [1] */
@@ -14964,7 +15000,7 @@
     /* op -<T : fia_fiu32_f16, N : num>(T, vec<N, T>) -> vec<N, T> */
     /* op -<T : fa_f32_f16, N : num, M : num>(mat<N, M, T>, mat<N, M, T>) -> mat<N, M, T> */
     /* num overloads */ 5,
-    /* overloads */ &kOverloads[236],
+    /* overloads */ &kOverloads[241],
   },
   {
     /* [2] */
@@ -14987,7 +15023,7 @@
     /* op /<T : fia_fiu32_f16, N : num>(vec<N, T>, T) -> vec<N, T> */
     /* op /<T : fia_fiu32_f16, N : num>(T, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 4,
-    /* overloads */ &kOverloads[241],
+    /* overloads */ &kOverloads[246],
   },
   {
     /* [4] */
@@ -14996,14 +15032,14 @@
     /* op %<T : fia_fiu32_f16, N : num>(vec<N, T>, T) -> vec<N, T> */
     /* op %<T : fia_fiu32_f16, N : num>(T, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 4,
-    /* overloads */ &kOverloads[245],
+    /* overloads */ &kOverloads[250],
   },
   {
     /* [5] */
     /* op ^<T : ia_iu32>(T, T) -> T */
     /* op ^<T : ia_iu32, N : num>(vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[413],
+    /* overloads */ &kOverloads[415],
   },
   {
     /* [6] */
@@ -15012,7 +15048,7 @@
     /* op &<T : ia_iu32>(T, T) -> T */
     /* op &<T : ia_iu32, N : num>(vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 4,
-    /* overloads */ &kOverloads[249],
+    /* overloads */ &kOverloads[254],
   },
   {
     /* [7] */
@@ -15021,75 +15057,75 @@
     /* op |<T : ia_iu32>(T, T) -> T */
     /* op |<T : ia_iu32, N : num>(vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 4,
-    /* overloads */ &kOverloads[253],
+    /* overloads */ &kOverloads[258],
   },
   {
     /* [8] */
     /* op &&(bool, bool) -> bool */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[467],
+    /* overloads */ &kOverloads[469],
   },
   {
     /* [9] */
     /* op ||(bool, bool) -> bool */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[468],
+    /* overloads */ &kOverloads[470],
   },
   {
     /* [10] */
     /* op ==<T : scalar>(T, T) -> bool */
     /* op ==<T : scalar, N : num>(vec<N, T>, vec<N, T>) -> vec<N, bool> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[415],
+    /* overloads */ &kOverloads[417],
   },
   {
     /* [11] */
     /* op !=<T : scalar>(T, T) -> bool */
     /* op !=<T : scalar, N : num>(vec<N, T>, vec<N, T>) -> vec<N, bool> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[417],
+    /* overloads */ &kOverloads[419],
   },
   {
     /* [12] */
     /* op <<T : fia_fiu32_f16>(T, T) -> bool */
     /* op <<T : fia_fiu32_f16, N : num>(vec<N, T>, vec<N, T>) -> vec<N, bool> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[419],
+    /* overloads */ &kOverloads[421],
   },
   {
     /* [13] */
     /* op ><T : fia_fiu32_f16>(T, T) -> bool */
     /* op ><T : fia_fiu32_f16, N : num>(vec<N, T>, vec<N, T>) -> vec<N, bool> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[421],
+    /* overloads */ &kOverloads[423],
   },
   {
     /* [14] */
     /* op <=<T : fia_fiu32_f16>(T, T) -> bool */
     /* op <=<T : fia_fiu32_f16, N : num>(vec<N, T>, vec<N, T>) -> vec<N, bool> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[423],
+    /* overloads */ &kOverloads[425],
   },
   {
     /* [15] */
     /* op >=<T : fia_fiu32_f16>(T, T) -> bool */
     /* op >=<T : fiu32_f16, N : num>(vec<N, T>, vec<N, T>) -> vec<N, bool> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[425],
+    /* overloads */ &kOverloads[427],
   },
   {
     /* [16] */
     /* op <<<T : ia_iu32>(T, u32) -> T */
     /* op <<<T : ia_iu32, N : num>(vec<N, T>, vec<N, u32>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[427],
+    /* overloads */ &kOverloads[429],
   },
   {
     /* [17] */
     /* op >><T : ia_iu32>(T, u32) -> T */
     /* op >><T : ia_iu32, N : num>(vec<N, T>, vec<N, u32>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[429],
+    /* overloads */ &kOverloads[431],
   },
 };
 constexpr uint8_t kBinaryOperatorPlus = 0;
@@ -15118,7 +15154,7 @@
     /* ctor i32(i32) -> i32 */
     /* conv i32<T : scalar_no_i32>(T) -> i32 */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[266],
+    /* overloads */ &kOverloads[268],
   },
   {
     /* [1] */
@@ -15126,7 +15162,7 @@
     /* ctor u32(u32) -> u32 */
     /* conv u32<T : scalar_no_u32>(T) -> u32 */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[269],
+    /* overloads */ &kOverloads[271],
   },
   {
     /* [2] */
@@ -15134,7 +15170,7 @@
     /* ctor f32(f32) -> f32 */
     /* conv f32<T : scalar_no_f32>(T) -> f32 */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[272],
+    /* overloads */ &kOverloads[274],
   },
   {
     /* [3] */
@@ -15142,7 +15178,7 @@
     /* ctor f16(f16) -> f16 */
     /* conv f16<T : scalar_no_f16>(T) -> f16 */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[275],
+    /* overloads */ &kOverloads[277],
   },
   {
     /* [4] */
@@ -15150,7 +15186,7 @@
     /* ctor bool(bool) -> bool */
     /* conv bool<T : scalar_no_bool>(T) -> bool */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[278],
+    /* overloads */ &kOverloads[280],
   },
   {
     /* [5] */
@@ -15305,7 +15341,7 @@
     /* [17] */
     /* conv packedVec3<T : concrete_scalar>(vec3<T>) -> packedVec3<T> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[469],
+    /* overloads */ &kOverloads[471],
   },
 };
 
diff --git a/src/tint/resolver/intrinsic_table_test.cc b/src/tint/resolver/intrinsic_table_test.cc
index 7f0399d..c842af5 100644
--- a/src/tint/resolver/intrinsic_table_test.cc
+++ b/src/tint/resolver/intrinsic_table_test.cc
@@ -37,7 +37,6 @@
 
 using ::testing::HasSubstr;
 
-using BuiltinType = sem::BuiltinType;
 using Parameter = sem::Parameter;
 using ParameterUsage = sem::ParameterUsage;
 
@@ -54,11 +53,11 @@
 
 TEST_F(IntrinsicTableTest, MatchF32) {
     auto* f32 = create<type::F32>();
-    auto result = table->Lookup(BuiltinType::kCos, utils::Vector{f32},
+    auto result = table->Lookup(builtin::Function::kCos, utils::Vector{f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kCos);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kCos);
     EXPECT_EQ(result.sem->ReturnType(), f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 1u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), f32);
@@ -66,7 +65,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchF32) {
     auto* i32 = create<type::I32>();
-    auto result = table->Lookup(BuiltinType::kCos, utils::Vector{i32},
+    auto result = table->Lookup(builtin::Function::kCos, utils::Vector{i32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -76,11 +75,11 @@
     auto* f32 = create<type::F32>();
     auto* u32 = create<type::U32>();
     auto* vec2_f32 = create<type::Vector>(f32, 2u);
-    auto result = table->Lookup(BuiltinType::kUnpack2X16Float, utils::Vector{u32},
+    auto result = table->Lookup(builtin::Function::kUnpack2X16Float, utils::Vector{u32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kUnpack2X16Float);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kUnpack2X16Float);
     EXPECT_EQ(result.sem->ReturnType(), vec2_f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 1u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), u32);
@@ -88,7 +87,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchU32) {
     auto* f32 = create<type::F32>();
-    auto result = table->Lookup(BuiltinType::kUnpack2X16Float, utils::Vector{f32},
+    auto result = table->Lookup(builtin::Function::kUnpack2X16Float, utils::Vector{f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -99,11 +98,11 @@
     auto* i32 = create<type::I32>();
     auto* vec4_f32 = create<type::Vector>(f32, 4u);
     auto* tex = create<type::SampledTexture>(type::TextureDimension::k1d, f32);
-    auto result = table->Lookup(BuiltinType::kTextureLoad, utils::Vector{tex, i32, i32},
+    auto result = table->Lookup(builtin::Function::kTextureLoad, utils::Vector{tex, i32, i32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kTextureLoad);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kTextureLoad);
     EXPECT_EQ(result.sem->ReturnType(), vec4_f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), tex);
@@ -117,7 +116,7 @@
 TEST_F(IntrinsicTableTest, MismatchI32) {
     auto* f32 = create<type::F32>();
     auto* tex = create<type::SampledTexture>(type::TextureDimension::k1d, f32);
-    auto result = table->Lookup(BuiltinType::kTextureLoad, utils::Vector{tex, f32},
+    auto result = table->Lookup(builtin::Function::kTextureLoad, utils::Vector{tex, f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -125,11 +124,11 @@
 
 TEST_F(IntrinsicTableTest, MatchIU32AsI32) {
     auto* i32 = create<type::I32>();
-    auto result = table->Lookup(BuiltinType::kCountOneBits, utils::Vector{i32},
+    auto result = table->Lookup(builtin::Function::kCountOneBits, utils::Vector{i32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kCountOneBits);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kCountOneBits);
     EXPECT_EQ(result.sem->ReturnType(), i32);
     ASSERT_EQ(result.sem->Parameters().Length(), 1u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), i32);
@@ -137,11 +136,11 @@
 
 TEST_F(IntrinsicTableTest, MatchIU32AsU32) {
     auto* u32 = create<type::U32>();
-    auto result = table->Lookup(BuiltinType::kCountOneBits, utils::Vector{u32},
+    auto result = table->Lookup(builtin::Function::kCountOneBits, utils::Vector{u32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kCountOneBits);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kCountOneBits);
     EXPECT_EQ(result.sem->ReturnType(), u32);
     ASSERT_EQ(result.sem->Parameters().Length(), 1u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), u32);
@@ -149,7 +148,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchIU32) {
     auto* f32 = create<type::F32>();
-    auto result = table->Lookup(BuiltinType::kCountOneBits, utils::Vector{f32},
+    auto result = table->Lookup(builtin::Function::kCountOneBits, utils::Vector{f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -157,11 +156,11 @@
 
 TEST_F(IntrinsicTableTest, MatchFIU32AsI32) {
     auto* i32 = create<type::I32>();
-    auto result = table->Lookup(BuiltinType::kClamp, utils::Vector{i32, i32, i32},
+    auto result = table->Lookup(builtin::Function::kClamp, utils::Vector{i32, i32, i32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kClamp);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kClamp);
     EXPECT_EQ(result.sem->ReturnType(), i32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), i32);
@@ -171,11 +170,11 @@
 
 TEST_F(IntrinsicTableTest, MatchFIU32AsU32) {
     auto* u32 = create<type::U32>();
-    auto result = table->Lookup(BuiltinType::kClamp, utils::Vector{u32, u32, u32},
+    auto result = table->Lookup(builtin::Function::kClamp, utils::Vector{u32, u32, u32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kClamp);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kClamp);
     EXPECT_EQ(result.sem->ReturnType(), u32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), u32);
@@ -185,11 +184,11 @@
 
 TEST_F(IntrinsicTableTest, MatchFIU32AsF32) {
     auto* f32 = create<type::F32>();
-    auto result = table->Lookup(BuiltinType::kClamp, utils::Vector{f32, f32, f32},
+    auto result = table->Lookup(builtin::Function::kClamp, utils::Vector{f32, f32, f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kClamp);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kClamp);
     EXPECT_EQ(result.sem->ReturnType(), f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), f32);
@@ -199,7 +198,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchFIU32) {
     auto* bool_ = create<type::Bool>();
-    auto result = table->Lookup(BuiltinType::kClamp, utils::Vector{bool_, bool_, bool_},
+    auto result = table->Lookup(builtin::Function::kClamp, utils::Vector{bool_, bool_, bool_},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -208,11 +207,11 @@
 TEST_F(IntrinsicTableTest, MatchBool) {
     auto* f32 = create<type::F32>();
     auto* bool_ = create<type::Bool>();
-    auto result = table->Lookup(BuiltinType::kSelect, utils::Vector{f32, f32, bool_},
+    auto result = table->Lookup(builtin::Function::kSelect, utils::Vector{f32, f32, bool_},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kSelect);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kSelect);
     EXPECT_EQ(result.sem->ReturnType(), f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), f32);
@@ -222,7 +221,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchBool) {
     auto* f32 = create<type::F32>();
-    auto result = table->Lookup(BuiltinType::kSelect, utils::Vector{f32, f32, f32},
+    auto result = table->Lookup(builtin::Function::kSelect, utils::Vector{f32, f32, f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -233,11 +232,11 @@
     auto* atomicI32 = create<type::Atomic>(i32);
     auto* ptr = create<type::Pointer>(atomicI32, builtin::AddressSpace::kWorkgroup,
                                       builtin::Access::kReadWrite);
-    auto result = table->Lookup(BuiltinType::kAtomicLoad, utils::Vector{ptr},
+    auto result = table->Lookup(builtin::Function::kAtomicLoad, utils::Vector{ptr},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kAtomicLoad);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kAtomicLoad);
     EXPECT_EQ(result.sem->ReturnType(), i32);
     ASSERT_EQ(result.sem->Parameters().Length(), 1u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), ptr);
@@ -246,7 +245,7 @@
 TEST_F(IntrinsicTableTest, MismatchPointer) {
     auto* i32 = create<type::I32>();
     auto* atomicI32 = create<type::Atomic>(i32);
-    auto result = table->Lookup(BuiltinType::kAtomicLoad, utils::Vector{atomicI32},
+    auto result = table->Lookup(builtin::Function::kAtomicLoad, utils::Vector{atomicI32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -257,11 +256,11 @@
         create<type::Array>(create<type::U32>(), create<type::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     auto* arr_ptr =
         create<type::Pointer>(arr, builtin::AddressSpace::kStorage, builtin::Access::kReadWrite);
-    auto result = table->Lookup(BuiltinType::kArrayLength, utils::Vector{arr_ptr},
+    auto result = table->Lookup(builtin::Function::kArrayLength, utils::Vector{arr_ptr},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kArrayLength);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kArrayLength);
     EXPECT_TRUE(result.sem->ReturnType()->Is<type::U32>());
     ASSERT_EQ(result.sem->Parameters().Length(), 1u);
     auto* param_type = result.sem->Parameters()[0]->Type();
@@ -271,7 +270,7 @@
 
 TEST_F(IntrinsicTableTest, MismatchArray) {
     auto* f32 = create<type::F32>();
-    auto result = table->Lookup(BuiltinType::kArrayLength, utils::Vector{f32},
+    auto result = table->Lookup(builtin::Function::kArrayLength, utils::Vector{f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -283,11 +282,12 @@
     auto* vec4_f32 = create<type::Vector>(f32, 4u);
     auto* tex = create<type::SampledTexture>(type::TextureDimension::k2d, f32);
     auto* sampler = create<type::Sampler>(type::SamplerKind::kSampler);
-    auto result = table->Lookup(BuiltinType::kTextureSample, utils::Vector{tex, sampler, vec2_f32},
-                                sem::EvaluationStage::kConstant, Source{});
+    auto result =
+        table->Lookup(builtin::Function::kTextureSample, utils::Vector{tex, sampler, vec2_f32},
+                      sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kTextureSample);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kTextureSample);
     EXPECT_EQ(result.sem->ReturnType(), vec4_f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), tex);
@@ -302,8 +302,9 @@
     auto* f32 = create<type::F32>();
     auto* vec2_f32 = create<type::Vector>(f32, 2u);
     auto* tex = create<type::SampledTexture>(type::TextureDimension::k2d, f32);
-    auto result = table->Lookup(BuiltinType::kTextureSample, utils::Vector{tex, f32, vec2_f32},
-                                sem::EvaluationStage::kConstant, Source{});
+    auto result =
+        table->Lookup(builtin::Function::kTextureSample, utils::Vector{tex, f32, vec2_f32},
+                      sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
@@ -314,11 +315,11 @@
     auto* vec2_i32 = create<type::Vector>(i32, 2u);
     auto* vec4_f32 = create<type::Vector>(f32, 4u);
     auto* tex = create<type::SampledTexture>(type::TextureDimension::k2d, f32);
-    auto result = table->Lookup(BuiltinType::kTextureLoad, utils::Vector{tex, vec2_i32, i32},
+    auto result = table->Lookup(builtin::Function::kTextureLoad, utils::Vector{tex, vec2_i32, i32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kTextureLoad);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kTextureLoad);
     EXPECT_EQ(result.sem->ReturnType(), vec4_f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), tex);
@@ -335,11 +336,11 @@
     auto* vec2_i32 = create<type::Vector>(i32, 2u);
     auto* vec4_f32 = create<type::Vector>(f32, 4u);
     auto* tex = create<type::MultisampledTexture>(type::TextureDimension::k2d, f32);
-    auto result = table->Lookup(BuiltinType::kTextureLoad, utils::Vector{tex, vec2_i32, i32},
+    auto result = table->Lookup(builtin::Function::kTextureLoad, utils::Vector{tex, vec2_i32, i32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kTextureLoad);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kTextureLoad);
     EXPECT_EQ(result.sem->ReturnType(), vec4_f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), tex);
@@ -355,11 +356,11 @@
     auto* i32 = create<type::I32>();
     auto* vec2_i32 = create<type::Vector>(i32, 2u);
     auto* tex = create<type::DepthTexture>(type::TextureDimension::k2d);
-    auto result = table->Lookup(BuiltinType::kTextureLoad, utils::Vector{tex, vec2_i32, i32},
+    auto result = table->Lookup(builtin::Function::kTextureLoad, utils::Vector{tex, vec2_i32, i32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kTextureLoad);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kTextureLoad);
     EXPECT_EQ(result.sem->ReturnType(), f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), tex);
@@ -375,11 +376,11 @@
     auto* i32 = create<type::I32>();
     auto* vec2_i32 = create<type::Vector>(i32, 2u);
     auto* tex = create<type::DepthMultisampledTexture>(type::TextureDimension::k2d);
-    auto result = table->Lookup(BuiltinType::kTextureLoad, utils::Vector{tex, vec2_i32, i32},
+    auto result = table->Lookup(builtin::Function::kTextureLoad, utils::Vector{tex, vec2_i32, i32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kTextureLoad);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kTextureLoad);
     EXPECT_EQ(result.sem->ReturnType(), f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), tex);
@@ -396,11 +397,11 @@
     auto* vec2_i32 = create<type::Vector>(i32, 2u);
     auto* vec4_f32 = create<type::Vector>(f32, 4u);
     auto* tex = create<type::ExternalTexture>();
-    auto result = table->Lookup(BuiltinType::kTextureLoad, utils::Vector{tex, vec2_i32},
+    auto result = table->Lookup(builtin::Function::kTextureLoad, utils::Vector{tex, vec2_i32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kTextureLoad);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kTextureLoad);
     EXPECT_EQ(result.sem->ReturnType(), vec4_f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 2u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), tex);
@@ -419,11 +420,12 @@
         create<type::StorageTexture>(type::TextureDimension::k2d, builtin::TexelFormat::kR32Float,
                                      builtin::Access::kWrite, subtype);
 
-    auto result = table->Lookup(BuiltinType::kTextureStore, utils::Vector{tex, vec2_i32, vec4_f32},
-                                sem::EvaluationStage::kConstant, Source{});
+    auto result =
+        table->Lookup(builtin::Function::kTextureStore, utils::Vector{tex, vec2_i32, vec4_f32},
+                      sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kTextureStore);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kTextureStore);
     EXPECT_TRUE(result.sem->ReturnType()->Is<type::Void>());
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), tex);
@@ -438,7 +440,7 @@
     auto* f32 = create<type::F32>();
     auto* i32 = create<type::I32>();
     auto* vec2_i32 = create<type::Vector>(i32, 2u);
-    auto result = table->Lookup(BuiltinType::kTextureLoad, utils::Vector{f32, vec2_i32},
+    auto result = table->Lookup(builtin::Function::kTextureLoad, utils::Vector{f32, vec2_i32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -446,7 +448,7 @@
 
 TEST_F(IntrinsicTableTest, ImplicitLoadOnReference) {
     auto* f32 = create<type::F32>();
-    auto result = table->Lookup(BuiltinType::kCos,
+    auto result = table->Lookup(builtin::Function::kCos,
                                 utils::Vector{
                                     create<type::Reference>(f32, builtin::AddressSpace::kFunction,
                                                             builtin::Access::kReadWrite),
@@ -454,7 +456,7 @@
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kCos);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kCos);
     EXPECT_EQ(result.sem->ReturnType(), f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 1u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), f32);
@@ -462,11 +464,11 @@
 
 TEST_F(IntrinsicTableTest, MatchTemplateType) {
     auto* f32 = create<type::F32>();
-    auto result = table->Lookup(BuiltinType::kClamp, utils::Vector{f32, f32, f32},
+    auto result = table->Lookup(builtin::Function::kClamp, utils::Vector{f32, f32, f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kClamp);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kClamp);
     EXPECT_EQ(result.sem->ReturnType(), f32);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), f32);
     EXPECT_EQ(result.sem->Parameters()[1]->Type(), f32);
@@ -476,7 +478,7 @@
 TEST_F(IntrinsicTableTest, MismatchTemplateType) {
     auto* f32 = create<type::F32>();
     auto* u32 = create<type::U32>();
-    auto result = table->Lookup(BuiltinType::kClamp, utils::Vector{f32, u32, f32},
+    auto result = table->Lookup(builtin::Function::kClamp, utils::Vector{f32, u32, f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -485,11 +487,12 @@
 TEST_F(IntrinsicTableTest, MatchOpenSizeVector) {
     auto* f32 = create<type::F32>();
     auto* vec2_f32 = create<type::Vector>(f32, 2u);
-    auto result = table->Lookup(BuiltinType::kClamp, utils::Vector{vec2_f32, vec2_f32, vec2_f32},
-                                sem::EvaluationStage::kConstant, Source{});
+    auto result =
+        table->Lookup(builtin::Function::kClamp, utils::Vector{vec2_f32, vec2_f32, vec2_f32},
+                      sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kClamp);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kClamp);
     EXPECT_EQ(result.sem->ReturnType(), vec2_f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), vec2_f32);
@@ -501,7 +504,7 @@
     auto* f32 = create<type::F32>();
     auto* u32 = create<type::U32>();
     auto* vec2_f32 = create<type::Vector>(f32, 2u);
-    auto result = table->Lookup(BuiltinType::kClamp, utils::Vector{vec2_f32, u32, vec2_f32},
+    auto result = table->Lookup(builtin::Function::kClamp, utils::Vector{vec2_f32, u32, vec2_f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -511,11 +514,11 @@
     auto* f32 = create<type::F32>();
     auto* vec3_f32 = create<type::Vector>(f32, 3u);
     auto* mat3_f32 = create<type::Matrix>(vec3_f32, 3u);
-    auto result = table->Lookup(BuiltinType::kDeterminant, utils::Vector{mat3_f32},
+    auto result = table->Lookup(builtin::Function::kDeterminant, utils::Vector{mat3_f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kDeterminant);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kDeterminant);
     EXPECT_EQ(result.sem->ReturnType(), f32);
     ASSERT_EQ(result.sem->Parameters().Length(), 1u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), mat3_f32);
@@ -525,7 +528,7 @@
     auto* f32 = create<type::F32>();
     auto* vec2_f32 = create<type::Vector>(f32, 2u);
     auto* mat3x2_f32 = create<type::Matrix>(vec2_f32, 3u);
-    auto result = table->Lookup(BuiltinType::kDeterminant, utils::Vector{mat3x2_f32},
+    auto result = table->Lookup(builtin::Function::kDeterminant, utils::Vector{mat3x2_f32},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -534,12 +537,12 @@
 TEST_F(IntrinsicTableTest, MatchDifferentArgsElementType_Builtin_ConstantEval) {
     auto* af = create<type::AbstractFloat>();
     auto* bool_ = create<type::Bool>();
-    auto result = table->Lookup(BuiltinType::kSelect, utils::Vector{af, af, bool_},
+    auto result = table->Lookup(builtin::Function::kSelect, utils::Vector{af, af, bool_},
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result.sem->Stage(), sem::EvaluationStage::kConstant);
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kSelect);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kSelect);
     EXPECT_EQ(result.sem->ReturnType(), af);
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_EQ(result.sem->Parameters()[0]->Type(), af);
@@ -551,12 +554,12 @@
     auto* af = create<type::AbstractFloat>();
     auto* bool_ref = create<type::Reference>(create<type::Bool>(), builtin::AddressSpace::kFunction,
                                              builtin::Access::kReadWrite);
-    auto result = table->Lookup(BuiltinType::kSelect, utils::Vector{af, af, bool_ref},
+    auto result = table->Lookup(builtin::Function::kSelect, utils::Vector{af, af, bool_ref},
                                 sem::EvaluationStage::kRuntime, Source{});
     ASSERT_NE(result.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result.sem->Stage(), sem::EvaluationStage::kConstant);
-    EXPECT_EQ(result.sem->Type(), BuiltinType::kSelect);
+    EXPECT_EQ(result.sem->Type(), builtin::Function::kSelect);
     EXPECT_TRUE(result.sem->ReturnType()->Is<type::F32>());
     ASSERT_EQ(result.sem->Parameters().Length(), 3u);
     EXPECT_TRUE(result.sem->Parameters()[0]->Type()->Is<type::F32>());
@@ -594,7 +597,7 @@
     // None of the arguments match, so expect the overloads with 2 parameters to
     // come first
     auto* bool_ = create<type::Bool>();
-    table->Lookup(BuiltinType::kTextureDimensions, utils::Vector{bool_, bool_},
+    table->Lookup(builtin::Function::kTextureDimensions, utils::Vector{bool_, bool_},
                   sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(Diagnostics().str(),
               R"(error: no matching call to textureDimensions(bool, bool)
@@ -633,7 +636,7 @@
 TEST_F(IntrinsicTableTest, OverloadOrderByMatchingParameter) {
     auto* tex = create<type::DepthTexture>(type::TextureDimension::k2d);
     auto* bool_ = create<type::Bool>();
-    table->Lookup(BuiltinType::kTextureDimensions, utils::Vector{tex, bool_},
+    table->Lookup(builtin::Function::kTextureDimensions, utils::Vector{tex, bool_},
                   sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(Diagnostics().str(),
               R"(error: no matching call to textureDimensions(texture_depth_2d, bool)
@@ -673,16 +676,16 @@
     auto* f32 = create<type::F32>();
     auto* vec2_f32 = create<type::Vector>(create<type::F32>(), 2u);
     auto* bool_ = create<type::Bool>();
-    auto a = table->Lookup(BuiltinType::kSelect, utils::Vector{f32, f32, bool_},
+    auto a = table->Lookup(builtin::Function::kSelect, utils::Vector{f32, f32, bool_},
                            sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(a.sem, nullptr) << Diagnostics().str();
 
-    auto b = table->Lookup(BuiltinType::kSelect, utils::Vector{f32, f32, bool_},
+    auto b = table->Lookup(builtin::Function::kSelect, utils::Vector{f32, f32, bool_},
                            sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(b.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
 
-    auto c = table->Lookup(BuiltinType::kSelect, utils::Vector{vec2_f32, vec2_f32, bool_},
+    auto c = table->Lookup(builtin::Function::kSelect, utils::Vector{vec2_f32, vec2_f32, bool_},
                            sem::EvaluationStage::kConstant, Source{});
     ASSERT_NE(c.sem, nullptr) << Diagnostics().str();
     ASSERT_EQ(Diagnostics().str(), "");
@@ -1022,7 +1025,7 @@
     auto* f32 = create<type::F32>();
     utils::Vector<const type::Type*, 0> arg_tys;
     arg_tys.Resize(257, f32);
-    auto result = table->Lookup(BuiltinType::kAbs, std::move(arg_tys),
+    auto result = table->Lookup(builtin::Function::kAbs, std::move(arg_tys),
                                 sem::EvaluationStage::kConstant, Source{});
     ASSERT_EQ(result.sem, nullptr);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
@@ -1261,7 +1264,7 @@
     auto* arg_a = GetParam().arg_a(*this);
     auto* arg_b = GetParam().arg_b(*this);
     auto* arg_c = GetParam().arg_c(*this);
-    auto builtin = table->Lookup(sem::BuiltinType::kClamp, utils::Vector{arg_a, arg_b, arg_c},
+    auto builtin = table->Lookup(builtin::Function::kClamp, utils::Vector{arg_a, arg_b, arg_c},
                                  sem::EvaluationStage::kConstant, Source{{12, 34}});
 
     bool matched = builtin.sem != nullptr;
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index 839389a..6fa2010 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -16,6 +16,7 @@
 
 #include "src/tint/resolver/resolver.h"
 #include "src/tint/resolver/resolver_test_helper.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/test_helper.h"
 
 #include "gmock/gmock.h"
@@ -939,7 +940,7 @@
             break;
         }
         case Method::kTintMaterializeBuiltin: {
-            auto* call = Call(sem::str(sem::BuiltinType::kTintMaterialize), abstract_expr());
+            auto* call = Call(builtin::str(builtin::Function::kTintMaterialize), abstract_expr());
             WrapInFunction(Decl(Const("c", call)));
             break;
         }
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index f51e5cc..2cd40e9 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -260,7 +260,7 @@
         ty = rhs->Type()->UnwrapRef();  // Implicit load of RHS
     }
 
-    if (rhs && !validator_.VariableInitializer(v, builtin::AddressSpace::kUndefined, ty, rhs)) {
+    if (rhs && !validator_.VariableInitializer(v, ty, rhs)) {
         return nullptr;
     }
 
@@ -323,7 +323,7 @@
         return nullptr;
     }
 
-    if (rhs && !validator_.VariableInitializer(v, builtin::AddressSpace::kUndefined, ty, rhs)) {
+    if (rhs && !validator_.VariableInitializer(v, ty, rhs)) {
         return nullptr;
     }
 
@@ -417,7 +417,7 @@
         ty = rhs->Type();
     }
 
-    if (!validator_.VariableInitializer(c, builtin::AddressSpace::kUndefined, ty, rhs)) {
+    if (!validator_.VariableInitializer(c, ty, rhs)) {
         return nullptr;
     }
 
@@ -516,7 +516,7 @@
         access = DefaultAccessForAddressSpace(address_space);
     }
 
-    if (rhs && !validator_.VariableInitializer(var, address_space, storage_ty, rhs)) {
+    if (rhs && !validator_.VariableInitializer(var, storage_ty, rhs)) {
         return nullptr;
     }
 
@@ -2156,7 +2156,12 @@
             return Switch(
                 sem_.Get(ast_node),  //
                 [&](type::Type* t) { return ty_init_or_conv(t); },
-                [&](sem::Function* f) { return FunctionCall(expr, f, args, arg_behaviors); },
+                [&](sem::Function* f) -> sem::Call* {
+                    if (!TINT_LIKELY(CheckNotTemplated("function", ident))) {
+                        return nullptr;
+                    }
+                    return FunctionCall(expr, f, args, arg_behaviors);
+                },
                 [&](sem::Expression* e) {
                     sem_.ErrorUnexpectedExprKind(e, "call target");
                     return nullptr;
@@ -2167,7 +2172,10 @@
                 });
         }
 
-        if (auto f = resolved->BuiltinFunction(); f != sem::BuiltinType::kNone) {
+        if (auto f = resolved->BuiltinFunction(); f != builtin::Function::kNone) {
+            if (!TINT_LIKELY(CheckNotTemplated("builtin", ident))) {
+                return nullptr;
+            }
             return BuiltinCall(expr, f, args);
         }
 
@@ -2239,7 +2247,7 @@
 
 template <size_t N>
 sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
-                                 sem::BuiltinType builtin_type,
+                                 builtin::Function builtin_type,
                                  utils::Vector<const sem::ValueExpression*, N>& args) {
     auto arg_stage = sem::EvaluationStage::kConstant;
     for (auto* arg : args) {
@@ -2255,7 +2263,7 @@
         }
     }
 
-    if (builtin_type == sem::BuiltinType::kTintMaterialize) {
+    if (builtin_type == builtin::Function::kTintMaterialize) {
         args[0] = Materialize(args[0]);
         if (!args[0]) {
             return nullptr;
@@ -2307,14 +2315,14 @@
         return nullptr;
     }
 
-    if (IsTextureBuiltin(builtin_type)) {
+    if (sem::IsTextureBuiltin(builtin_type)) {
         if (!validator_.TextureBuiltinFunction(call)) {
             return nullptr;
         }
         CollectTextureSamplerPairs(builtin.sem, call->Arguments());
     }
 
-    if (builtin_type == sem::BuiltinType::kWorkgroupUniformLoad) {
+    if (builtin_type == builtin::Function::kWorkgroupUniformLoad) {
         if (!validator_.WorkgroupUniformLoad(call)) {
             return nullptr;
         }
@@ -2331,13 +2339,7 @@
     auto& b = *builder_;
 
     auto check_no_tmpl_args = [&](type::Type* ty) -> type::Type* {
-        if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
-            AddError("type '" + b.Symbols().NameFor(ident->symbol) +
-                         "' does not take template arguments",
-                     ident->source);
-            return nullptr;
-        }
-        return ty;
+        return TINT_LIKELY(CheckNotTemplated("type", ident)) ? ty : nullptr;
     };
     auto f32 = [&] { return b.create<type::F32>(); };
     auto i32 = [&] { return b.create<type::I32>(); };
@@ -3019,25 +3021,15 @@
                 return user;
             },
             [&](const type::Type* ty) -> sem::TypeExpression* {
-                if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
-                    AddError("type '" + builder_->Symbols().NameFor(ident->symbol) +
-                                 "' does not take template arguments",
-                             ident->source);
-                    sem_.NoteDeclarationSource(ast_node);
+                if (!TINT_LIKELY(CheckNotTemplated("type", ident))) {
                     return nullptr;
                 }
-
                 return builder_->create<sem::TypeExpression>(expr, current_statement_, ty);
             },
             [&](const sem::Function* fn) -> sem::FunctionExpression* {
-                if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
-                    AddError("function '" + builder_->Symbols().NameFor(ident->symbol) +
-                                 "' does not take template arguments",
-                             ident->source);
-                    sem_.NoteDeclarationSource(ast_node);
+                if (!TINT_LIKELY(CheckNotTemplated("function", ident))) {
                     return nullptr;
                 }
-
                 return builder_->create<sem::FunctionExpression>(expr, current_statement_, fn);
             });
     }
@@ -3050,7 +3042,7 @@
         return builder_->create<sem::TypeExpression>(expr, current_statement_, ty);
     }
 
-    if (resolved->BuiltinFunction() != sem::BuiltinType::kNone) {
+    if (resolved->BuiltinFunction() != builtin::Function::kNone) {
         AddError("missing '(' for builtin function call", expr->source.End());
         return nullptr;
     }
@@ -3484,7 +3476,10 @@
 }
 
 bool Resolver::Enable(const ast::Enable* enable) {
-    enabled_extensions_.Add(enable->extension);
+    for (auto* ext : enable->extensions) {
+        Mark(ext);
+        enabled_extensions_.Add(ext->name);
+    }
     return true;
 }
 
@@ -4282,6 +4277,16 @@
             [&](const ast::BlockStatement* block) {
                 return handle_attributes(block, sem, "block statements");
             },
+            [&](const ast::ForLoopStatement* f) {
+                return handle_attributes(f, sem, "for statements");
+            },
+            [&](const ast::IfStatement* i) { return handle_attributes(i, sem, "if statements"); },
+            [&](const ast::SwitchStatement* s) {
+                return handle_attributes(s, sem, "switch statements");
+            },
+            [&](const ast::WhileStatement* w) {
+                return handle_attributes(w, sem, "while statements");
+            },
             [&](Default) { return true; })) {
         return nullptr;
     }
@@ -4329,6 +4334,21 @@
     }
 }
 
+bool Resolver::CheckNotTemplated(const char* use, const ast::Identifier* ident) {
+    if (TINT_UNLIKELY(ident->Is<ast::TemplatedIdentifier>())) {
+        AddError(std::string(use) + " '" + builder_->Symbols().NameFor(ident->symbol) +
+                     "' does not take template arguments",
+                 ident->source);
+        if (auto resolved = dependencies_.resolved_identifiers.Get(ident)) {
+            if (auto* ast_node = resolved->Node()) {
+                sem_.NoteDeclarationSource(ast_node);
+            }
+        }
+        return false;
+    }
+    return true;
+}
+
 void Resolver::ErrorMismatchedResolvedIdentifier(const Source& source,
                                                  const ResolvedIdentifier& resolved,
                                                  std::string_view wanted) {
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index 8acf602..266f3a2 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -204,7 +204,7 @@
     sem::Expression* Identifier(const ast::IdentifierExpression*);
     template <size_t N>
     sem::Call* BuiltinCall(const ast::CallExpression*,
-                           sem::BuiltinType,
+                           builtin::Function,
                            utils::Vector<const sem::ValueExpression*, N>& args);
     sem::ValueExpression* Literal(const ast::LiteralExpression*);
     sem::ValueExpression* MemberAccessor(const ast::MemberAccessorExpression*);
@@ -480,6 +480,11 @@
     template <typename NODE>
     void ApplyDiagnosticSeverities(NODE* node);
 
+    /// Checks @p ident is not an ast::TemplatedIdentifier.
+    /// If @p ident is a ast::TemplatedIdentifier, then an error diagnostic is raised.
+    /// @returns true if @p ident is not a ast::TemplatedIdentifier.
+    bool CheckNotTemplated(const char* use, const ast::Identifier* ident);
+
     /// Raises an error diagnostic that the resolved identifier @p resolved was not of the expected
     /// kind.
     /// @param source the source of the error diagnostic
diff --git a/src/tint/resolver/sem_helper.cc b/src/tint/resolver/sem_helper.cc
index 45bdf41..36cc4c7 100644
--- a/src/tint/resolver/sem_helper.cc
+++ b/src/tint/resolver/sem_helper.cc
@@ -19,6 +19,7 @@
 #include "src/tint/sem/function_expression.h"
 #include "src/tint/sem/type_expression.h"
 #include "src/tint/sem/value_expression.h"
+#include "src/tint/switch.h"
 
 namespace tint::resolver {
 
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index bc4ac1d..7a4d25e 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -37,7 +37,9 @@
 #include "src/tint/sem/value_conversion.h"
 #include "src/tint/sem/variable.h"
 #include "src/tint/sem/while_statement.h"
+#include "src/tint/switch.h"
 #include "src/tint/utils/block_allocator.h"
+#include "src/tint/utils/defer.h"
 #include "src/tint/utils/map.h"
 #include "src/tint/utils/string_stream.h"
 #include "src/tint/utils/unique_vector.h"
@@ -45,6 +47,10 @@
 // Set to `1` to dump the uniformity graph for each function in graphviz format.
 #define TINT_DUMP_UNIFORMITY_GRAPH 0
 
+#if TINT_DUMP_UNIFORMITY_GRAPH
+#include <iostream>
+#endif
+
 namespace tint::resolver {
 
 namespace {
@@ -123,7 +129,7 @@
     const ast::Node* ast = nullptr;
 
     /// The function call argument index, if applicable.
-    uint32_t arg_index;
+    uint32_t arg_index = 0xffffffffu;
 
     /// The set of edges from this node to other nodes in the graph.
     utils::UniqueVector<Node*, 4> edges;
@@ -547,14 +553,18 @@
             stmt,
 
             [&](const ast::AssignmentStatement* a) {
-                auto [cf1, v1] = ProcessExpression(cf, a->rhs);
                 if (a->lhs->Is<ast::PhonyExpression>()) {
-                    return cf1;
-                } else {
-                    auto [cf2, l2] = ProcessLValueExpression(cf1, a->lhs);
-                    l2->AddEdge(v1);
-                    return cf2;
+                    auto [cf_r, _] = ProcessExpression(cf, a->rhs);
+                    return cf_r;
                 }
+                auto [cf_l, v_l, ident] = ProcessLValueExpression(cf, a->lhs);
+                auto [cf_r, v_r] = ProcessExpression(cf_l, a->rhs);
+                v_l->AddEdge(v_r);
+
+                // Update the variable node for the LHS variable.
+                current_function_->variables.Set(ident, v_l);
+
+                return cf_r;
             },
 
             [&](const ast::BlockStatement* b) {
@@ -696,18 +706,31 @@
             },
 
             [&](const ast::CompoundAssignmentStatement* c) {
-                // The compound assignment statement `a += b` is equivalent to `a = a + b`.
-                // Note: we set load_rule=true when evaluating the LHS the first time, as the
-                // resolver does not add a load node for it.
-                auto [cf1, v1] = ProcessExpression(cf, c->lhs, /* load_rule */ true);
-                auto [cf2, v2] = ProcessExpression(cf1, c->rhs);
-                auto* result = CreateNode({"binary_expr_result"});
-                result->AddEdge(v1);
-                result->AddEdge(v2);
+                // The compound assignment statement `a += b` is equivalent to:
+                //   let p = &a;
+                //   *p = *p + b;
 
-                auto [cf3, l3] = ProcessLValueExpression(cf2, c->lhs);
-                l3->AddEdge(result);
-                return cf3;
+                // Evaluate the LHS.
+                auto [cf1, l1, ident] = ProcessLValueExpression(cf, c->lhs);
+
+                // Get the current value loaded from the LHS reference before evaluating the RHS.
+                auto* lhs_load = current_function_->variables.Get(ident);
+
+                // Evaluate the RHS.
+                auto [cf2, v2] = ProcessExpression(cf1, c->rhs);
+
+                // Create a node for the resulting value.
+                auto* result = CreateNode({"binary_expr_result"});
+                result->AddEdge(v2);
+                if (lhs_load) {
+                    result->AddEdge(lhs_load);
+                }
+
+                // Update the variable node for the LHS variable.
+                l1->AddEdge(result);
+                current_function_->variables.Set(ident, l1);
+
+                return cf2;
             },
 
             [&](const ast::ContinueStatement* c) {
@@ -958,16 +981,25 @@
 
             [&](const ast::IncrementDecrementStatement* i) {
                 // The increment/decrement statement `i++` is equivalent to `i = i + 1`.
-                // Note: we set load_rule=true when evaluating the LHS the first time, as the
-                // resolver does not add a load node for it.
-                auto [cf1, v1] = ProcessExpression(cf, i->lhs, /* load_rule */ true);
-                auto* result = CreateNode({"incdec_result"});
-                result->AddEdge(v1);
-                result->AddEdge(cf1);
 
-                auto [cf2, l2] = ProcessLValueExpression(cf1, i->lhs);
-                l2->AddEdge(result);
-                return cf2;
+                // Evaluate the LHS.
+                auto [cf1, l1, ident] = ProcessLValueExpression(cf, i->lhs);
+
+                // Get the current value loaded from the LHS reference.
+                auto* lhs_load = current_function_->variables.Get(ident);
+
+                // Create a node for the resulting value.
+                auto* result = CreateNode({"incdec_result"});
+                result->AddEdge(cf1);
+                if (lhs_load) {
+                    result->AddEdge(lhs_load);
+                }
+
+                // Update the variable node for the LHS variable.
+                l1->AddEdge(result);
+                current_function_->variables.Set(ident, l1);
+
+                return cf1;
             },
 
             [&](const ast::LoopStatement* l) {
@@ -1365,48 +1397,60 @@
         return false;
     }
 
+    /// LValue holds the Nodes returned by ProcessLValueExpression()
+    struct LValue {
+        /// The control-flow node for an LValue expression
+        Node* cf = nullptr;
+
+        /// The new value node for an LValue expression
+        Node* new_val = nullptr;
+
+        /// The root identifier for an LValue expression.
+        const sem::Variable* root_identifier = nullptr;
+    };
+
     /// Process an LValue expression.
     /// @param cf the input control flow node
     /// @param expr the expression to process
     /// @returns a pair of (control flow node, variable node)
-    std::pair<Node*, Node*> ProcessLValueExpression(Node* cf,
-                                                    const ast::Expression* expr,
-                                                    bool is_partial_reference = false) {
+    LValue ProcessLValueExpression(Node* cf,
+                                   const ast::Expression* expr,
+                                   bool is_partial_reference = false) {
         return Switch(
             expr,
 
             [&](const ast::IdentifierExpression* i) {
                 auto* sem = sem_.GetVal(i)->UnwrapLoad()->As<sem::VariableUser>();
                 if (sem->Variable()->Is<sem::GlobalVariable>()) {
-                    return std::make_pair(cf, current_function_->may_be_non_uniform);
+                    return LValue{cf, current_function_->may_be_non_uniform, nullptr};
                 } else if (auto* local = sem->Variable()->As<sem::LocalVariable>()) {
                     // Create a new value node for this variable.
                     auto* value = CreateNode({NameFor(i), "_lvalue"});
-                    auto* old_value = current_function_->variables.Set(local, value);
 
                     // If i is part of an expression that is a partial reference to a variable (e.g.
                     // index or member access), we link back to the variable's previous value. If
                     // the previous value was non-uniform, a partial assignment will not make it
                     // uniform.
+                    auto* old_value = current_function_->variables.Get(local);
                     if (is_partial_reference && old_value) {
                         value->AddEdge(old_value);
                     }
 
-                    return std::make_pair(cf, value);
+                    return LValue{cf, value, local};
                 } else {
                     TINT_ICE(Resolver, diagnostics_)
                         << "unknown lvalue identifier expression type: "
                         << std::string(sem->Variable()->TypeInfo().name);
-                    return std::pair<Node*, Node*>(nullptr, nullptr);
+                    return LValue{};
                 }
             },
 
             [&](const ast::IndexAccessorExpression* i) {
-                auto [cf1, l1] =
+                auto [cf1, l1, root_ident] =
                     ProcessLValueExpression(cf, i->object, /*is_partial_reference*/ true);
                 auto [cf2, v2] = ProcessExpression(cf1, i->index);
                 l1->AddEdge(v2);
-                return std::pair<Node*, Node*>(cf2, l1);
+                return LValue{cf2, l1, root_ident};
             },
 
             [&](const ast::MemberAccessorExpression* m) {
@@ -1419,17 +1463,16 @@
                     // that is being written to.
                     auto* root_ident = sem_.Get(u)->RootIdentifier();
                     auto* deref = CreateNode({NameFor(root_ident), "_deref"});
-                    auto* old_value = current_function_->variables.Set(root_ident, deref);
 
-                    if (old_value) {
-                        // If derefercing a partial reference or partial pointer, we link back to
+                    if (auto* old_value = current_function_->variables.Get(root_ident)) {
+                        // If dereferencing a partial reference or partial pointer, we link back to
                         // the variable's previous value. If the previous value was non-uniform, a
                         // partial assignment will not make it uniform.
                         if (is_partial_reference || IsDerefOfPartialPointer(u)) {
                             deref->AddEdge(old_value);
                         }
                     }
-                    return std::pair<Node*, Node*>(cf, deref);
+                    return LValue{cf, deref, root_ident};
                 }
                 return ProcessLValueExpression(cf, u->expr, is_partial_reference);
             },
@@ -1437,7 +1480,7 @@
             [&](Default) {
                 TINT_ICE(Resolver, diagnostics_)
                     << "unknown lvalue expression type: " << std::string(expr->TypeInfo().name);
-                return std::pair<Node*, Node*>(nullptr, nullptr);
+                return LValue{};
             });
     }
 
@@ -1514,12 +1557,12 @@
                 // some texture sampling builtins, and atomics.
                 if (builtin->IsBarrier()) {
                     callsite_tag = {CallSiteTag::CallSiteRequiredToBeUniform, default_severity};
-                } else if (builtin->Type() == sem::BuiltinType::kWorkgroupUniformLoad) {
+                } else if (builtin->Type() == builtin::Function::kWorkgroupUniformLoad) {
                     callsite_tag = {CallSiteTag::CallSiteRequiredToBeUniform, default_severity};
                 } else if (builtin->IsDerivative() ||
-                           builtin->Type() == sem::BuiltinType::kTextureSample ||
-                           builtin->Type() == sem::BuiltinType::kTextureSampleBias ||
-                           builtin->Type() == sem::BuiltinType::kTextureSampleCompare) {
+                           builtin->Type() == builtin::Function::kTextureSample ||
+                           builtin->Type() == builtin::Function::kTextureSampleBias ||
+                           builtin->Type() == builtin::Function::kTextureSampleCompare) {
                     // Get the severity of derivative uniformity violations in this context.
                     auto severity = sem_.DiagnosticSeverity(
                         call, builtin::DiagnosticRule::kDerivativeUniformity);
@@ -1626,7 +1669,7 @@
                 }
             } else {
                 auto* builtin = sem->Target()->As<sem::Builtin>();
-                if (builtin && builtin->Type() == sem::BuiltinType::kWorkgroupUniformLoad) {
+                if (builtin && builtin->Type() == builtin::Function::kWorkgroupUniformLoad) {
                     // The workgroupUniformLoad builtin requires its parameter to be uniform.
                     current_function_->RequiredToBeUniform(default_severity)->AddEdge(args[i]);
                 } else {
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
index 51608bd..e166a06 100644
--- a/src/tint/resolver/uniformity_test.cc
+++ b/src/tint/resolver/uniformity_test.cc
@@ -7402,6 +7402,377 @@
 )");
 }
 
+TEST_F(UniformityAnalysisTest, CompoundAssignment_Global) {
+    // Use compound assignment on a global variable.
+    // Tests that we do not assume there is always a variable node for the LHS, but we still process
+    // the expression.
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+var<private> v : array<i32, 4>;
+
+fn bar(p : ptr<function, i32>) -> i32 {
+  if (*p == 0) {
+    workgroupBarrier();
+  }
+  return 0;
+}
+
+fn foo() {
+  var f = rw;
+  v[bar(&f)] += 1;
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:8:5 error: 'workgroupBarrier' must only be called from uniform control flow
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^
+
+test:7:3 note: control flow depends on possibly non-uniform value
+  if (*p == 0) {
+  ^^
+
+test:7:8 note: parameter 'p' of 'bar' may be non-uniform
+  if (*p == 0) {
+       ^
+
+test:15:9 note: possibly non-uniform value passed via pointer here
+  v[bar(&f)] += 1;
+        ^
+
+test:14:11 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+  var f = rw;
+          ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IncDec_StillNonUniform) {
+    // Use increment on a variable that is already non-uniform.
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+  var v = rw;
+  v++;
+  if (v == 0) {
+    workgroupBarrier();
+  }
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:8:5 error: 'workgroupBarrier' must only be called from uniform control flow
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^
+
+test:7:3 note: control flow depends on possibly non-uniform value
+  if (v == 0) {
+  ^^
+
+test:5:11 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+  var v = rw;
+          ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, IncDec_Global) {
+    // Use increment on a global variable.
+    // Tests that we do not assume there is always a variable node for the LHS, but we still process
+    // the expression.
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+var<private> v : array<i32, 4>;
+
+fn bar(p : ptr<function, i32>) -> i32 {
+  if (*p == 0) {
+    workgroupBarrier();
+  }
+  return 0;
+}
+
+fn foo() {
+  var f = rw;
+  v[bar(&f)]++;
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:8:5 error: 'workgroupBarrier' must only be called from uniform control flow
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^
+
+test:7:3 note: control flow depends on possibly non-uniform value
+  if (*p == 0) {
+  ^^
+
+test:7:8 note: parameter 'p' of 'bar' may be non-uniform
+  if (*p == 0) {
+       ^
+
+test:15:9 note: possibly non-uniform value passed via pointer here
+  v[bar(&f)]++;
+        ^
+
+test:14:11 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+  var f = rw;
+          ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, AssignmentEval_LHS_Then_RHS_Pass) {
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn b(p : ptr<function, i32>) -> i32 {
+  *p = non_uniform;
+  return 0;
+}
+
+fn a(p : ptr<function, i32>) -> i32 {
+  if (*p == 0) {
+    workgroupBarrier();
+  }
+  return 0;
+}
+
+fn foo() {
+  var i = 0;
+  var arr : array<i32, 4>;
+  arr[a(&i)] = arr[b(&i)];
+}
+)";
+
+    RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, AssignmentEval_LHS_Then_RHS_Fail) {
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn a(p : ptr<function, i32>) -> i32 {
+  *p = non_uniform;
+  return 0;
+}
+
+fn b(p : ptr<function, i32>) -> i32 {
+  if (*p == 0) {
+    workgroupBarrier();
+  }
+  return 0;
+}
+
+fn foo() {
+  var i = 0;
+  var arr : array<i32, 4>;
+  arr[a(&i)] = arr[b(&i)];
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:11:5 error: 'workgroupBarrier' must only be called from uniform control flow
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^
+
+test:10:3 note: control flow depends on possibly non-uniform value
+  if (*p == 0) {
+  ^^
+
+test:10:8 note: parameter 'p' of 'b' may be non-uniform
+  if (*p == 0) {
+       ^
+
+test:19:22 note: possibly non-uniform value passed via pointer here
+  arr[a(&i)] = arr[b(&i)];
+                     ^
+
+test:19:9 note: contents of pointer may become non-uniform after calling 'a'
+  arr[a(&i)] = arr[b(&i)];
+        ^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, CompoundAssignmentEval_LHS_Then_RHS_Pass) {
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn b(p : ptr<function, i32>) -> i32 {
+  *p = non_uniform;
+  return 0;
+}
+
+fn a(p : ptr<function, i32>) -> i32 {
+  if (*p == 0) {
+    workgroupBarrier();
+  }
+  return 0;
+}
+
+fn foo() {
+  var i = 0;
+  var arr : array<i32, 4>;
+  arr[a(&i)] += arr[b(&i)];
+}
+)";
+
+    RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, CompoundAssignmentEval_LHS_Then_RHS_Fail) {
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn a(p : ptr<function, i32>) -> i32 {
+  *p = non_uniform;
+  return 0;
+}
+
+fn b(p : ptr<function, i32>) -> i32 {
+  if (*p == 0) {
+    workgroupBarrier();
+  }
+  return 0;
+}
+
+fn foo() {
+  var i = 0;
+  var arr : array<i32, 4>;
+  arr[a(&i)] += arr[b(&i)];
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:11:5 error: 'workgroupBarrier' must only be called from uniform control flow
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^
+
+test:10:3 note: control flow depends on possibly non-uniform value
+  if (*p == 0) {
+  ^^
+
+test:10:8 note: parameter 'p' of 'b' may be non-uniform
+  if (*p == 0) {
+       ^
+
+test:19:23 note: possibly non-uniform value passed via pointer here
+  arr[a(&i)] += arr[b(&i)];
+                      ^
+
+test:19:9 note: contents of pointer may become non-uniform after calling 'a'
+  arr[a(&i)] += arr[b(&i)];
+        ^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, CompoundAssignmentEval_RHS_Makes_LHS_NonUniform_After_Load) {
+    // Test that the LHS is loaded from before the RHS makes is evaluated.
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) -> i32 {
+  *p = non_uniform;
+  return 0;
+}
+
+fn foo() {
+  var i = 0;
+  var arr : array<i32, 4>;
+  i += arr[bar(&i)];
+  if (i == 0) {
+    workgroupBarrier();
+  }
+}
+)";
+
+    RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, CompoundAssignmentEval_RHS_Makes_LHS_Uniform_After_Load) {
+    // Test that the LHS is loaded from before the RHS makes is evaluated.
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) -> i32 {
+  *p = 0;
+  return 0;
+}
+
+fn foo() {
+  var i = non_uniform;
+  var arr : array<i32, 4>;
+  i += arr[bar(&i)];
+  if (i == 0) {
+    workgroupBarrier();
+  }
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:14:5 error: 'workgroupBarrier' must only be called from uniform control flow
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^
+
+test:13:3 note: control flow depends on possibly non-uniform value
+  if (i == 0) {
+  ^^
+
+test:10:11 note: reading from read_write storage buffer 'non_uniform' may result in a non-uniform value
+  var i = non_uniform;
+          ^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, CompoundAssignmentEval_LHS_OnlyOnce) {
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) -> i32 {
+  if (*p == 0) {
+    workgroupBarrier();
+  }
+  *p = non_uniform;
+  return 0;
+}
+
+fn foo(){
+  var f : i32 = 0;
+  var arr : array<i32, 4>;
+  arr[bar(&f)] += 1;
+}
+)";
+
+    RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, IncDec_LHS_OnlyOnce) {
+    std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+
+fn bar(p : ptr<function, i32>) -> i32 {
+  if (*p == 0) {
+    workgroupBarrier();
+  }
+  *p = non_uniform;
+  return 0;
+}
+
+fn foo(){
+  var f : i32 = 0;
+  var arr : array<i32, 4>;
+  arr[bar(&f)]++;
+}
+)";
+
+    RunTest(src, true);
+}
+
 TEST_F(UniformityAnalysisTest, ShortCircuiting_UniformLHS) {
     std::string src = R"(
 @group(0) @binding(0) var<storage, read> uniform_global : i32;
@@ -8005,6 +8376,278 @@
     }
 }
 
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnForStatement_CallInInitializer) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(for (var b = (non_uniform == 42 && dpdx(1.0) > 0.0); false;) {
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'dpdx' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnForStatement_CallInCondition) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(for (; non_uniform == 42 && dpdx(1.0) > 0.0;) {
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'dpdx' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnForStatement_CallInIncrement) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(for (var b = false; false; b = (non_uniform == 42 && dpdx(1.0) > 0.0)) {
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'dpdx' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnForStatement_CallInBody) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(1) var t : texture_2d<f32>;
+@group(0) @binding(2) var s : sampler;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(for (; non_uniform == 42;) {
+    let color = textureSample(t, s, vec2(0, 0));
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'textureSample' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnIfStatement_CallInCondition) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(if (non_uniform == 42 && dpdx(1.0) > 0.0) {
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'dpdx' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnIfStatement_CallInBody) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(1) var t : texture_2d<f32>;
+@group(0) @binding(2) var s : sampler;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(if (non_uniform == 42) {
+    let color = textureSample(t, s, vec2(0, 0));
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'textureSample' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnIfStatement_CallInElse) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(1) var t : texture_2d<f32>;
+@group(0) @binding(2) var s : sampler;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(if (non_uniform == 42) {
+  } else {
+    let color = textureSample(t, s, vec2(0, 0));
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'textureSample' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnSwitchStatement_CallInCondition) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(switch (i32(non_uniform == 42 && dpdx(1.0) > 0.0)) {
+    default {}
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'dpdx' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnSwitchStatement_CallInBody) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(1) var t : texture_2d<f32>;
+@group(0) @binding(2) var s : sampler;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(switch (non_uniform) {
+    default {
+      let color = textureSample(t, s, vec2(0, 0));
+    }
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'textureSample' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnWhileStatement_CallInCondition) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(while (non_uniform == 42 && dpdx(1.0) > 0.0) {
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'dpdx' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnWhileStatement_CallInBody) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(1) var t : texture_2d<f32>;
+@group(0) @binding(2) var s : sampler;
+fn foo() {
+  )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"(while (non_uniform == 42) {
+    let color = textureSample(t, s, vec2(0, 0));
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'textureSample' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
 INSTANTIATE_TEST_SUITE_P(UniformityAnalysisTest,
                          UniformityAnalysisDiagnosticFilterTest,
                          ::testing::Values(builtin::DiagnosticSeverity::kError,
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index f8f4c4d..c3b8a3d 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -373,7 +373,6 @@
 }
 
 bool Validator::VariableInitializer(const ast::Variable* v,
-                                    builtin::AddressSpace address_space,
                                     const type::Type* storage_ty,
                                     const sem::ValueExpression* initializer) const {
     auto* initializer_ty = initializer->Type();
@@ -388,23 +387,6 @@
         return false;
     }
 
-    if (v->Is<ast::Var>()) {
-        switch (address_space) {
-            case builtin::AddressSpace::kPrivate:
-            case builtin::AddressSpace::kFunction:
-                break;  // Allowed an initializer
-            default:
-                // https://gpuweb.github.io/gpuweb/wgsl/#var-and-let
-                // Optionally has an initializer expression, if the variable is in the private or
-                // function address spaces.
-                AddError("var of address space '" + utils::ToString(address_space) +
-                             "' cannot have an initializer. var initializers are only supported "
-                             "for the address spaces 'private' and 'function'",
-                         v->source);
-                return false;
-        }
-    }
-
     return true;
 }
 
@@ -728,6 +710,23 @@
         }
     }
 
+    if (var->initializer) {
+        switch (v->AddressSpace()) {
+            case builtin::AddressSpace::kPrivate:
+            case builtin::AddressSpace::kFunction:
+                break;  // Allowed an initializer
+            default:
+                // https://gpuweb.github.io/gpuweb/wgsl/#var-and-let
+                // Optionally has an initializer expression, if the variable is in the private or
+                // function address spaces.
+                AddError("var of address space '" + utils::ToString(v->AddressSpace()) +
+                             "' cannot have an initializer. var initializers are only supported "
+                             "for the address spaces 'private' and 'function'",
+                         var->source);
+                return false;
+        }
+    }
+
     if (!CheckTypeAccessAddressSpace(v->Type()->UnwrapRef(), v->Access(), v->AddressSpace(),
                                      var->attributes, var->source)) {
         return false;
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index 45c98c8..e0e3051 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -429,12 +429,10 @@
 
     /// Validates a variable initializer
     /// @param v the variable to validate
-    /// @param address_space the address space of the variable
     /// @param storage_type the type of the storage
     /// @param initializer the RHS initializer expression
     /// @returns true on succes, false otherwise
     bool VariableInitializer(const ast::Variable* v,
-                             builtin::AddressSpace address_space,
                              const type::Type* storage_type,
                              const sem::ValueExpression* initializer) const;
 
diff --git a/src/tint/resolver/variable_validation_test.cc b/src/tint/resolver/variable_validation_test.cc
index 6ca3a08..7f62e8e 100644
--- a/src/tint/resolver/variable_validation_test.cc
+++ b/src/tint/resolver/variable_validation_test.cc
@@ -58,6 +58,26 @@
     EXPECT_EQ(r()->error(), "12:34 error: builtin 'storageBarrier' does not return a value");
 }
 
+TEST_F(ResolverVariableValidationTest, GlobalVarNoAddressSpace) {
+    // var a : i32;
+    GlobalVar(Source{{12, 34}}, "a", ty.i32());
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: module-scope 'var' declarations that are not of texture or sampler types must provide an address space)");
+}
+
+TEST_F(ResolverVariableValidationTest, GlobalVarWithInitializerNoAddressSpace) {
+    // var a = 1;
+    GlobalVar(Source{{12, 34}}, "a", Expr(1_a));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: module-scope 'var' declarations that are not of texture or sampler types must provide an address space)");
+}
+
 TEST_F(ResolverVariableValidationTest, GlobalVarUsedAtModuleScope) {
     // var<private> a : i32;
     // var<private> b : i32 = a;
diff --git a/src/tint/sem/builtin.cc b/src/tint/sem/builtin.cc
index 4fd2c34..4f33ebb 100644
--- a/src/tint/sem/builtin.cc
+++ b/src/tint/sem/builtin.cc
@@ -27,74 +27,76 @@
 namespace tint::sem {
 
 const char* Builtin::str() const {
-    return sem::str(type_);
+    return builtin::str(type_);
 }
 
-bool IsCoarseDerivativeBuiltin(BuiltinType i) {
-    return i == BuiltinType::kDpdxCoarse || i == BuiltinType::kDpdyCoarse ||
-           i == BuiltinType::kFwidthCoarse;
+bool IsCoarseDerivativeBuiltin(builtin::Function i) {
+    return i == builtin::Function::kDpdxCoarse || i == builtin::Function::kDpdyCoarse ||
+           i == builtin::Function::kFwidthCoarse;
 }
 
-bool IsFineDerivativeBuiltin(BuiltinType i) {
-    return i == BuiltinType::kDpdxFine || i == BuiltinType::kDpdyFine ||
-           i == BuiltinType::kFwidthFine;
+bool IsFineDerivativeBuiltin(builtin::Function i) {
+    return i == builtin::Function::kDpdxFine || i == builtin::Function::kDpdyFine ||
+           i == builtin::Function::kFwidthFine;
 }
 
-bool IsDerivativeBuiltin(BuiltinType i) {
-    return i == BuiltinType::kDpdx || i == BuiltinType::kDpdy || i == BuiltinType::kFwidth ||
-           IsCoarseDerivativeBuiltin(i) || IsFineDerivativeBuiltin(i);
+bool IsDerivativeBuiltin(builtin::Function i) {
+    return i == builtin::Function::kDpdx || i == builtin::Function::kDpdy ||
+           i == builtin::Function::kFwidth || IsCoarseDerivativeBuiltin(i) ||
+           IsFineDerivativeBuiltin(i);
 }
 
-bool IsTextureBuiltin(BuiltinType i) {
-    return IsImageQueryBuiltin(i) ||                           //
-           i == BuiltinType::kTextureGather ||                 //
-           i == BuiltinType::kTextureGatherCompare ||          //
-           i == BuiltinType::kTextureLoad ||                   //
-           i == BuiltinType::kTextureSample ||                 //
-           i == BuiltinType::kTextureSampleBaseClampToEdge ||  //
-           i == BuiltinType::kTextureSampleBias ||             //
-           i == BuiltinType::kTextureSampleCompare ||          //
-           i == BuiltinType::kTextureSampleCompareLevel ||     //
-           i == BuiltinType::kTextureSampleGrad ||             //
-           i == BuiltinType::kTextureSampleLevel ||            //
-           i == BuiltinType::kTextureStore;
+bool IsTextureBuiltin(builtin::Function i) {
+    return IsImageQueryBuiltin(i) ||                                 //
+           i == builtin::Function::kTextureGather ||                 //
+           i == builtin::Function::kTextureGatherCompare ||          //
+           i == builtin::Function::kTextureLoad ||                   //
+           i == builtin::Function::kTextureSample ||                 //
+           i == builtin::Function::kTextureSampleBaseClampToEdge ||  //
+           i == builtin::Function::kTextureSampleBias ||             //
+           i == builtin::Function::kTextureSampleCompare ||          //
+           i == builtin::Function::kTextureSampleCompareLevel ||     //
+           i == builtin::Function::kTextureSampleGrad ||             //
+           i == builtin::Function::kTextureSampleLevel ||            //
+           i == builtin::Function::kTextureStore;
 }
 
-bool IsImageQueryBuiltin(BuiltinType i) {
-    return i == BuiltinType::kTextureDimensions || i == BuiltinType::kTextureNumLayers ||
-           i == BuiltinType::kTextureNumLevels || i == BuiltinType::kTextureNumSamples;
+bool IsImageQueryBuiltin(builtin::Function i) {
+    return i == builtin::Function::kTextureDimensions ||
+           i == builtin::Function::kTextureNumLayers || i == builtin::Function::kTextureNumLevels ||
+           i == builtin::Function::kTextureNumSamples;
 }
 
-bool IsDataPackingBuiltin(BuiltinType i) {
-    return i == BuiltinType::kPack4X8Snorm || i == BuiltinType::kPack4X8Unorm ||
-           i == BuiltinType::kPack2X16Snorm || i == BuiltinType::kPack2X16Unorm ||
-           i == BuiltinType::kPack2X16Float;
+bool IsDataPackingBuiltin(builtin::Function i) {
+    return i == builtin::Function::kPack4X8Snorm || i == builtin::Function::kPack4X8Unorm ||
+           i == builtin::Function::kPack2X16Snorm || i == builtin::Function::kPack2X16Unorm ||
+           i == builtin::Function::kPack2X16Float;
 }
 
-bool IsDataUnpackingBuiltin(BuiltinType i) {
-    return i == BuiltinType::kUnpack4X8Snorm || i == BuiltinType::kUnpack4X8Unorm ||
-           i == BuiltinType::kUnpack2X16Snorm || i == BuiltinType::kUnpack2X16Unorm ||
-           i == BuiltinType::kUnpack2X16Float;
+bool IsDataUnpackingBuiltin(builtin::Function i) {
+    return i == builtin::Function::kUnpack4X8Snorm || i == builtin::Function::kUnpack4X8Unorm ||
+           i == builtin::Function::kUnpack2X16Snorm || i == builtin::Function::kUnpack2X16Unorm ||
+           i == builtin::Function::kUnpack2X16Float;
 }
 
-bool IsBarrierBuiltin(BuiltinType i) {
-    return i == BuiltinType::kWorkgroupBarrier || i == BuiltinType::kStorageBarrier;
+bool IsBarrierBuiltin(builtin::Function i) {
+    return i == builtin::Function::kWorkgroupBarrier || i == builtin::Function::kStorageBarrier;
 }
 
-bool IsAtomicBuiltin(BuiltinType i) {
-    return i == sem::BuiltinType::kAtomicLoad || i == sem::BuiltinType::kAtomicStore ||
-           i == sem::BuiltinType::kAtomicAdd || i == sem::BuiltinType::kAtomicSub ||
-           i == sem::BuiltinType::kAtomicMax || i == sem::BuiltinType::kAtomicMin ||
-           i == sem::BuiltinType::kAtomicAnd || i == sem::BuiltinType::kAtomicOr ||
-           i == sem::BuiltinType::kAtomicXor || i == sem::BuiltinType::kAtomicExchange ||
-           i == sem::BuiltinType::kAtomicCompareExchangeWeak;
+bool IsAtomicBuiltin(builtin::Function i) {
+    return i == builtin::Function::kAtomicLoad || i == builtin::Function::kAtomicStore ||
+           i == builtin::Function::kAtomicAdd || i == builtin::Function::kAtomicSub ||
+           i == builtin::Function::kAtomicMax || i == builtin::Function::kAtomicMin ||
+           i == builtin::Function::kAtomicAnd || i == builtin::Function::kAtomicOr ||
+           i == builtin::Function::kAtomicXor || i == builtin::Function::kAtomicExchange ||
+           i == builtin::Function::kAtomicCompareExchangeWeak;
 }
 
-bool IsDP4aBuiltin(BuiltinType i) {
-    return i == sem::BuiltinType::kDot4I8Packed || i == sem::BuiltinType::kDot4U8Packed;
+bool IsDP4aBuiltin(builtin::Function i) {
+    return i == builtin::Function::kDot4I8Packed || i == builtin::Function::kDot4U8Packed;
 }
 
-Builtin::Builtin(BuiltinType type,
+Builtin::Builtin(builtin::Function type,
                  const type::Type* return_type,
                  utils::VectorRef<Parameter*> parameters,
                  EvaluationStage eval_stage,
@@ -150,18 +152,18 @@
 
 bool Builtin::HasSideEffects() const {
     switch (type_) {
-        case sem::BuiltinType::kAtomicAdd:
-        case sem::BuiltinType::kAtomicAnd:
-        case sem::BuiltinType::kAtomicCompareExchangeWeak:
-        case sem::BuiltinType::kAtomicExchange:
-        case sem::BuiltinType::kAtomicMax:
-        case sem::BuiltinType::kAtomicMin:
-        case sem::BuiltinType::kAtomicOr:
-        case sem::BuiltinType::kAtomicStore:
-        case sem::BuiltinType::kAtomicSub:
-        case sem::BuiltinType::kAtomicXor:
-        case sem::BuiltinType::kTextureStore:
-        case sem::BuiltinType::kWorkgroupUniformLoad:
+        case builtin::Function::kAtomicAdd:
+        case builtin::Function::kAtomicAnd:
+        case builtin::Function::kAtomicCompareExchangeWeak:
+        case builtin::Function::kAtomicExchange:
+        case builtin::Function::kAtomicMax:
+        case builtin::Function::kAtomicMin:
+        case builtin::Function::kAtomicOr:
+        case builtin::Function::kAtomicStore:
+        case builtin::Function::kAtomicSub:
+        case builtin::Function::kAtomicXor:
+        case builtin::Function::kTextureStore:
+        case builtin::Function::kWorkgroupUniformLoad:
             return true;
         default:
             break;
diff --git a/src/tint/sem/builtin.h b/src/tint/sem/builtin.h
index 55d882e..b535e72 100644
--- a/src/tint/sem/builtin.h
+++ b/src/tint/sem/builtin.h
@@ -19,7 +19,7 @@
 #include <vector>
 
 #include "src/tint/builtin/extension.h"
-#include "src/tint/sem/builtin_type.h"
+#include "src/tint/builtin/function.h"
 #include "src/tint/sem/call_target.h"
 #include "src/tint/sem/pipeline_stage_set.h"
 #include "src/tint/utils/hash.h"
@@ -29,52 +29,52 @@
 /// Determines if the given `i` is a coarse derivative
 /// @param i the builtin type
 /// @returns true if the given derivative is coarse.
-bool IsCoarseDerivativeBuiltin(BuiltinType i);
+bool IsCoarseDerivativeBuiltin(builtin::Function i);
 
 /// Determines if the given `i` is a fine derivative
 /// @param i the builtin type
 /// @returns true if the given derivative is fine.
-bool IsFineDerivativeBuiltin(BuiltinType i);
+bool IsFineDerivativeBuiltin(builtin::Function i);
 
 /// Determine if the given `i` is a derivative builtin
 /// @param i the builtin type
 /// @returns true if the given `i` is a derivative builtin
-bool IsDerivativeBuiltin(BuiltinType i);
+bool IsDerivativeBuiltin(builtin::Function i);
 
 /// Determines if the given `i` is a texture operation builtin
 /// @param i the builtin type
 /// @returns true if the given `i` is a texture operation builtin
-bool IsTextureBuiltin(BuiltinType i);
+bool IsTextureBuiltin(builtin::Function i);
 
 /// Determines if the given `i` is a image query builtin
 /// @param i the builtin type
 /// @returns true if the given `i` is a image query builtin
-bool IsImageQueryBuiltin(BuiltinType i);
+bool IsImageQueryBuiltin(builtin::Function i);
 
 /// Determines if the given `i` is a data packing builtin
 /// @param i the builtin
 /// @returns true if the given `i` is a data packing builtin
-bool IsDataPackingBuiltin(BuiltinType i);
+bool IsDataPackingBuiltin(builtin::Function i);
 
 /// Determines if the given `i` is a data unpacking builtin
 /// @param i the builtin
 /// @returns true if the given `i` is a data unpacking builtin
-bool IsDataUnpackingBuiltin(BuiltinType i);
+bool IsDataUnpackingBuiltin(builtin::Function i);
 
 /// Determines if the given `i` is a barrier builtin
 /// @param i the builtin
 /// @returns true if the given `i` is a barrier builtin
-bool IsBarrierBuiltin(BuiltinType i);
+bool IsBarrierBuiltin(builtin::Function i);
 
 /// Determines if the given `i` is a atomic builtin
 /// @param i the builtin
 /// @returns true if the given `i` is a atomic builtin
-bool IsAtomicBuiltin(BuiltinType i);
+bool IsAtomicBuiltin(builtin::Function i);
 
 /// Determins if the given `i` is a DP4a builtin
 /// @param i the builtin
 /// @returns true if the given `i` is a DP4a builtin
-bool IsDP4aBuiltin(BuiltinType i);
+bool IsDP4aBuiltin(builtin::Function i);
 
 /// Builtin holds the semantic information for a builtin function.
 class Builtin final : public Castable<Builtin, CallTarget> {
@@ -87,7 +87,7 @@
     /// @param supported_stages the pipeline stages that this builtin can be used in
     /// @param is_deprecated true if the particular overload is considered deprecated
     /// @param must_use true if the builtin was annotated with `@must_use`
-    Builtin(BuiltinType type,
+    Builtin(builtin::Function type,
             const type::Type* return_type,
             utils::VectorRef<Parameter*> parameters,
             EvaluationStage eval_stage,
@@ -99,7 +99,7 @@
     ~Builtin() override;
 
     /// @return the type of the builtin
-    BuiltinType Type() const { return type_; }
+    builtin::Function Type() const { return type_; }
 
     /// @return the pipeline stages that this builtin can be used in
     PipelineStageSet SupportedStages() const { return supported_stages_; }
@@ -151,7 +151,7 @@
     builtin::Extension RequiredExtension() const;
 
   private:
-    const BuiltinType type_;
+    const builtin::Function type_;
     const PipelineStageSet supported_stages_;
     const bool is_deprecated_;
 };
diff --git a/src/tint/sem/builtin_test.cc b/src/tint/sem/builtin_test.cc
index ff66e1b..ef63dc6 100644
--- a/src/tint/sem/builtin_test.cc
+++ b/src/tint/sem/builtin_test.cc
@@ -21,7 +21,7 @@
 
 struct BuiltinData {
     const char* name;
-    BuiltinType builtin;
+    builtin::Function builtin;
 };
 
 inline std::ostream& operator<<(std::ostream& out, BuiltinData data) {
@@ -29,98 +29,98 @@
     return out;
 }
 
-using BuiltinTypeTest = testing::TestWithParam<BuiltinData>;
+using BuiltinFunctionTest = testing::TestWithParam<BuiltinData>;
 
-TEST_P(BuiltinTypeTest, Parse) {
+TEST_P(BuiltinFunctionTest, Parse) {
     auto param = GetParam();
-    EXPECT_EQ(ParseBuiltinType(param.name), param.builtin);
+    EXPECT_EQ(builtin::ParseFunction(param.name), param.builtin);
 }
 
 INSTANTIATE_TEST_SUITE_P(
-    BuiltinTypeTest,
-    BuiltinTypeTest,
-    testing::Values(BuiltinData{"abs", BuiltinType::kAbs},
-                    BuiltinData{"acos", BuiltinType::kAcos},
-                    BuiltinData{"all", BuiltinType::kAll},
-                    BuiltinData{"any", BuiltinType::kAny},
-                    BuiltinData{"arrayLength", BuiltinType::kArrayLength},
-                    BuiltinData{"asin", BuiltinType::kAsin},
-                    BuiltinData{"atan", BuiltinType::kAtan},
-                    BuiltinData{"atan2", BuiltinType::kAtan2},
-                    BuiltinData{"ceil", BuiltinType::kCeil},
-                    BuiltinData{"clamp", BuiltinType::kClamp},
-                    BuiltinData{"cos", BuiltinType::kCos},
-                    BuiltinData{"cosh", BuiltinType::kCosh},
-                    BuiltinData{"countOneBits", BuiltinType::kCountOneBits},
-                    BuiltinData{"cross", BuiltinType::kCross},
-                    BuiltinData{"determinant", BuiltinType::kDeterminant},
-                    BuiltinData{"distance", BuiltinType::kDistance},
-                    BuiltinData{"dot", BuiltinType::kDot},
-                    BuiltinData{"dot4I8Packed", BuiltinType::kDot4I8Packed},
-                    BuiltinData{"dot4U8Packed", BuiltinType::kDot4U8Packed},
-                    BuiltinData{"dpdx", BuiltinType::kDpdx},
-                    BuiltinData{"dpdxCoarse", BuiltinType::kDpdxCoarse},
-                    BuiltinData{"dpdxFine", BuiltinType::kDpdxFine},
-                    BuiltinData{"dpdy", BuiltinType::kDpdy},
-                    BuiltinData{"dpdyCoarse", BuiltinType::kDpdyCoarse},
-                    BuiltinData{"dpdyFine", BuiltinType::kDpdyFine},
-                    BuiltinData{"exp", BuiltinType::kExp},
-                    BuiltinData{"exp2", BuiltinType::kExp2},
-                    BuiltinData{"faceForward", BuiltinType::kFaceForward},
-                    BuiltinData{"floor", BuiltinType::kFloor},
-                    BuiltinData{"fma", BuiltinType::kFma},
-                    BuiltinData{"fract", BuiltinType::kFract},
-                    BuiltinData{"frexp", BuiltinType::kFrexp},
-                    BuiltinData{"fwidth", BuiltinType::kFwidth},
-                    BuiltinData{"fwidthCoarse", BuiltinType::kFwidthCoarse},
-                    BuiltinData{"fwidthFine", BuiltinType::kFwidthFine},
-                    BuiltinData{"inverseSqrt", BuiltinType::kInverseSqrt},
-                    BuiltinData{"ldexp", BuiltinType::kLdexp},
-                    BuiltinData{"length", BuiltinType::kLength},
-                    BuiltinData{"log", BuiltinType::kLog},
-                    BuiltinData{"log2", BuiltinType::kLog2},
-                    BuiltinData{"max", BuiltinType::kMax},
-                    BuiltinData{"min", BuiltinType::kMin},
-                    BuiltinData{"mix", BuiltinType::kMix},
-                    BuiltinData{"modf", BuiltinType::kModf},
-                    BuiltinData{"normalize", BuiltinType::kNormalize},
-                    BuiltinData{"pow", BuiltinType::kPow},
-                    BuiltinData{"reflect", BuiltinType::kReflect},
-                    BuiltinData{"reverseBits", BuiltinType::kReverseBits},
-                    BuiltinData{"round", BuiltinType::kRound},
-                    BuiltinData{"select", BuiltinType::kSelect},
-                    BuiltinData{"sign", BuiltinType::kSign},
-                    BuiltinData{"sin", BuiltinType::kSin},
-                    BuiltinData{"sinh", BuiltinType::kSinh},
-                    BuiltinData{"smoothstep", BuiltinType::kSmoothstep},
-                    BuiltinData{"sqrt", BuiltinType::kSqrt},
-                    BuiltinData{"step", BuiltinType::kStep},
-                    BuiltinData{"storageBarrier", BuiltinType::kStorageBarrier},
-                    BuiltinData{"tan", BuiltinType::kTan},
-                    BuiltinData{"tanh", BuiltinType::kTanh},
-                    BuiltinData{"textureDimensions", BuiltinType::kTextureDimensions},
-                    BuiltinData{"textureLoad", BuiltinType::kTextureLoad},
-                    BuiltinData{"textureNumLayers", BuiltinType::kTextureNumLayers},
-                    BuiltinData{"textureNumLevels", BuiltinType::kTextureNumLevels},
-                    BuiltinData{"textureNumSamples", BuiltinType::kTextureNumSamples},
-                    BuiltinData{"textureSample", BuiltinType::kTextureSample},
-                    BuiltinData{"textureSampleBias", BuiltinType::kTextureSampleBias},
-                    BuiltinData{"textureSampleCompare", BuiltinType::kTextureSampleCompare},
+    BuiltinFunctionTest,
+    BuiltinFunctionTest,
+    testing::Values(BuiltinData{"abs", builtin::Function::kAbs},
+                    BuiltinData{"acos", builtin::Function::kAcos},
+                    BuiltinData{"all", builtin::Function::kAll},
+                    BuiltinData{"any", builtin::Function::kAny},
+                    BuiltinData{"arrayLength", builtin::Function::kArrayLength},
+                    BuiltinData{"asin", builtin::Function::kAsin},
+                    BuiltinData{"atan", builtin::Function::kAtan},
+                    BuiltinData{"atan2", builtin::Function::kAtan2},
+                    BuiltinData{"ceil", builtin::Function::kCeil},
+                    BuiltinData{"clamp", builtin::Function::kClamp},
+                    BuiltinData{"cos", builtin::Function::kCos},
+                    BuiltinData{"cosh", builtin::Function::kCosh},
+                    BuiltinData{"countOneBits", builtin::Function::kCountOneBits},
+                    BuiltinData{"cross", builtin::Function::kCross},
+                    BuiltinData{"determinant", builtin::Function::kDeterminant},
+                    BuiltinData{"distance", builtin::Function::kDistance},
+                    BuiltinData{"dot", builtin::Function::kDot},
+                    BuiltinData{"dot4I8Packed", builtin::Function::kDot4I8Packed},
+                    BuiltinData{"dot4U8Packed", builtin::Function::kDot4U8Packed},
+                    BuiltinData{"dpdx", builtin::Function::kDpdx},
+                    BuiltinData{"dpdxCoarse", builtin::Function::kDpdxCoarse},
+                    BuiltinData{"dpdxFine", builtin::Function::kDpdxFine},
+                    BuiltinData{"dpdy", builtin::Function::kDpdy},
+                    BuiltinData{"dpdyCoarse", builtin::Function::kDpdyCoarse},
+                    BuiltinData{"dpdyFine", builtin::Function::kDpdyFine},
+                    BuiltinData{"exp", builtin::Function::kExp},
+                    BuiltinData{"exp2", builtin::Function::kExp2},
+                    BuiltinData{"faceForward", builtin::Function::kFaceForward},
+                    BuiltinData{"floor", builtin::Function::kFloor},
+                    BuiltinData{"fma", builtin::Function::kFma},
+                    BuiltinData{"fract", builtin::Function::kFract},
+                    BuiltinData{"frexp", builtin::Function::kFrexp},
+                    BuiltinData{"fwidth", builtin::Function::kFwidth},
+                    BuiltinData{"fwidthCoarse", builtin::Function::kFwidthCoarse},
+                    BuiltinData{"fwidthFine", builtin::Function::kFwidthFine},
+                    BuiltinData{"inverseSqrt", builtin::Function::kInverseSqrt},
+                    BuiltinData{"ldexp", builtin::Function::kLdexp},
+                    BuiltinData{"length", builtin::Function::kLength},
+                    BuiltinData{"log", builtin::Function::kLog},
+                    BuiltinData{"log2", builtin::Function::kLog2},
+                    BuiltinData{"max", builtin::Function::kMax},
+                    BuiltinData{"min", builtin::Function::kMin},
+                    BuiltinData{"mix", builtin::Function::kMix},
+                    BuiltinData{"modf", builtin::Function::kModf},
+                    BuiltinData{"normalize", builtin::Function::kNormalize},
+                    BuiltinData{"pow", builtin::Function::kPow},
+                    BuiltinData{"reflect", builtin::Function::kReflect},
+                    BuiltinData{"reverseBits", builtin::Function::kReverseBits},
+                    BuiltinData{"round", builtin::Function::kRound},
+                    BuiltinData{"select", builtin::Function::kSelect},
+                    BuiltinData{"sign", builtin::Function::kSign},
+                    BuiltinData{"sin", builtin::Function::kSin},
+                    BuiltinData{"sinh", builtin::Function::kSinh},
+                    BuiltinData{"smoothstep", builtin::Function::kSmoothstep},
+                    BuiltinData{"sqrt", builtin::Function::kSqrt},
+                    BuiltinData{"step", builtin::Function::kStep},
+                    BuiltinData{"storageBarrier", builtin::Function::kStorageBarrier},
+                    BuiltinData{"tan", builtin::Function::kTan},
+                    BuiltinData{"tanh", builtin::Function::kTanh},
+                    BuiltinData{"textureDimensions", builtin::Function::kTextureDimensions},
+                    BuiltinData{"textureLoad", builtin::Function::kTextureLoad},
+                    BuiltinData{"textureNumLayers", builtin::Function::kTextureNumLayers},
+                    BuiltinData{"textureNumLevels", builtin::Function::kTextureNumLevels},
+                    BuiltinData{"textureNumSamples", builtin::Function::kTextureNumSamples},
+                    BuiltinData{"textureSample", builtin::Function::kTextureSample},
+                    BuiltinData{"textureSampleBias", builtin::Function::kTextureSampleBias},
+                    BuiltinData{"textureSampleCompare", builtin::Function::kTextureSampleCompare},
                     BuiltinData{"textureSampleCompareLevel",
-                                BuiltinType::kTextureSampleCompareLevel},
-                    BuiltinData{"textureSampleGrad", BuiltinType::kTextureSampleGrad},
-                    BuiltinData{"textureSampleLevel", BuiltinType::kTextureSampleLevel},
-                    BuiltinData{"trunc", BuiltinType::kTrunc},
-                    BuiltinData{"unpack2x16float", BuiltinType::kUnpack2X16Float},
-                    BuiltinData{"unpack2x16snorm", BuiltinType::kUnpack2X16Snorm},
-                    BuiltinData{"unpack2x16unorm", BuiltinType::kUnpack2X16Unorm},
-                    BuiltinData{"unpack4x8snorm", BuiltinType::kUnpack4X8Snorm},
-                    BuiltinData{"unpack4x8unorm", BuiltinType::kUnpack4X8Unorm},
-                    BuiltinData{"workgroupBarrier", BuiltinType::kWorkgroupBarrier},
-                    BuiltinData{"workgroupUniformLoad", BuiltinType::kWorkgroupUniformLoad}));
+                                builtin::Function::kTextureSampleCompareLevel},
+                    BuiltinData{"textureSampleGrad", builtin::Function::kTextureSampleGrad},
+                    BuiltinData{"textureSampleLevel", builtin::Function::kTextureSampleLevel},
+                    BuiltinData{"trunc", builtin::Function::kTrunc},
+                    BuiltinData{"unpack2x16float", builtin::Function::kUnpack2X16Float},
+                    BuiltinData{"unpack2x16snorm", builtin::Function::kUnpack2X16Snorm},
+                    BuiltinData{"unpack2x16unorm", builtin::Function::kUnpack2X16Unorm},
+                    BuiltinData{"unpack4x8snorm", builtin::Function::kUnpack4X8Snorm},
+                    BuiltinData{"unpack4x8unorm", builtin::Function::kUnpack4X8Unorm},
+                    BuiltinData{"workgroupBarrier", builtin::Function::kWorkgroupBarrier},
+                    BuiltinData{"workgroupUniformLoad", builtin::Function::kWorkgroupUniformLoad}));
 
-TEST_F(BuiltinTypeTest, ParseNoMatch) {
-    EXPECT_EQ(ParseBuiltinType("not_builtin"), BuiltinType::kNone);
+TEST_F(BuiltinFunctionTest, ParseNoMatch) {
+    EXPECT_EQ(builtin::ParseFunction("not_builtin"), builtin::Function::kNone);
 }
 
 }  // namespace
diff --git a/src/tint/sem/builtin_type.cc b/src/tint/sem/builtin_type.cc
deleted file mode 100644
index 8abcec0..0000000
--- a/src/tint/sem/builtin_type.cc
+++ /dev/null
@@ -1,614 +0,0 @@
-// 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.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by tools/src/cmd/gen
-// using the template:
-//   src/tint/sem/builtin_type.cc.tmpl
-//
-// Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#include "src/tint/sem/builtin_type.h"
-
-namespace tint::sem {
-
-BuiltinType ParseBuiltinType(const std::string& name) {
-    if (name == "abs") {
-        return BuiltinType::kAbs;
-    }
-    if (name == "acos") {
-        return BuiltinType::kAcos;
-    }
-    if (name == "acosh") {
-        return BuiltinType::kAcosh;
-    }
-    if (name == "all") {
-        return BuiltinType::kAll;
-    }
-    if (name == "any") {
-        return BuiltinType::kAny;
-    }
-    if (name == "arrayLength") {
-        return BuiltinType::kArrayLength;
-    }
-    if (name == "asin") {
-        return BuiltinType::kAsin;
-    }
-    if (name == "asinh") {
-        return BuiltinType::kAsinh;
-    }
-    if (name == "atan") {
-        return BuiltinType::kAtan;
-    }
-    if (name == "atan2") {
-        return BuiltinType::kAtan2;
-    }
-    if (name == "atanh") {
-        return BuiltinType::kAtanh;
-    }
-    if (name == "ceil") {
-        return BuiltinType::kCeil;
-    }
-    if (name == "clamp") {
-        return BuiltinType::kClamp;
-    }
-    if (name == "cos") {
-        return BuiltinType::kCos;
-    }
-    if (name == "cosh") {
-        return BuiltinType::kCosh;
-    }
-    if (name == "countLeadingZeros") {
-        return BuiltinType::kCountLeadingZeros;
-    }
-    if (name == "countOneBits") {
-        return BuiltinType::kCountOneBits;
-    }
-    if (name == "countTrailingZeros") {
-        return BuiltinType::kCountTrailingZeros;
-    }
-    if (name == "cross") {
-        return BuiltinType::kCross;
-    }
-    if (name == "degrees") {
-        return BuiltinType::kDegrees;
-    }
-    if (name == "determinant") {
-        return BuiltinType::kDeterminant;
-    }
-    if (name == "distance") {
-        return BuiltinType::kDistance;
-    }
-    if (name == "dot") {
-        return BuiltinType::kDot;
-    }
-    if (name == "dot4I8Packed") {
-        return BuiltinType::kDot4I8Packed;
-    }
-    if (name == "dot4U8Packed") {
-        return BuiltinType::kDot4U8Packed;
-    }
-    if (name == "dpdx") {
-        return BuiltinType::kDpdx;
-    }
-    if (name == "dpdxCoarse") {
-        return BuiltinType::kDpdxCoarse;
-    }
-    if (name == "dpdxFine") {
-        return BuiltinType::kDpdxFine;
-    }
-    if (name == "dpdy") {
-        return BuiltinType::kDpdy;
-    }
-    if (name == "dpdyCoarse") {
-        return BuiltinType::kDpdyCoarse;
-    }
-    if (name == "dpdyFine") {
-        return BuiltinType::kDpdyFine;
-    }
-    if (name == "exp") {
-        return BuiltinType::kExp;
-    }
-    if (name == "exp2") {
-        return BuiltinType::kExp2;
-    }
-    if (name == "extractBits") {
-        return BuiltinType::kExtractBits;
-    }
-    if (name == "faceForward") {
-        return BuiltinType::kFaceForward;
-    }
-    if (name == "firstLeadingBit") {
-        return BuiltinType::kFirstLeadingBit;
-    }
-    if (name == "firstTrailingBit") {
-        return BuiltinType::kFirstTrailingBit;
-    }
-    if (name == "floor") {
-        return BuiltinType::kFloor;
-    }
-    if (name == "fma") {
-        return BuiltinType::kFma;
-    }
-    if (name == "fract") {
-        return BuiltinType::kFract;
-    }
-    if (name == "frexp") {
-        return BuiltinType::kFrexp;
-    }
-    if (name == "fwidth") {
-        return BuiltinType::kFwidth;
-    }
-    if (name == "fwidthCoarse") {
-        return BuiltinType::kFwidthCoarse;
-    }
-    if (name == "fwidthFine") {
-        return BuiltinType::kFwidthFine;
-    }
-    if (name == "insertBits") {
-        return BuiltinType::kInsertBits;
-    }
-    if (name == "inverseSqrt") {
-        return BuiltinType::kInverseSqrt;
-    }
-    if (name == "ldexp") {
-        return BuiltinType::kLdexp;
-    }
-    if (name == "length") {
-        return BuiltinType::kLength;
-    }
-    if (name == "log") {
-        return BuiltinType::kLog;
-    }
-    if (name == "log2") {
-        return BuiltinType::kLog2;
-    }
-    if (name == "max") {
-        return BuiltinType::kMax;
-    }
-    if (name == "min") {
-        return BuiltinType::kMin;
-    }
-    if (name == "mix") {
-        return BuiltinType::kMix;
-    }
-    if (name == "modf") {
-        return BuiltinType::kModf;
-    }
-    if (name == "normalize") {
-        return BuiltinType::kNormalize;
-    }
-    if (name == "pack2x16float") {
-        return BuiltinType::kPack2X16Float;
-    }
-    if (name == "pack2x16snorm") {
-        return BuiltinType::kPack2X16Snorm;
-    }
-    if (name == "pack2x16unorm") {
-        return BuiltinType::kPack2X16Unorm;
-    }
-    if (name == "pack4x8snorm") {
-        return BuiltinType::kPack4X8Snorm;
-    }
-    if (name == "pack4x8unorm") {
-        return BuiltinType::kPack4X8Unorm;
-    }
-    if (name == "pow") {
-        return BuiltinType::kPow;
-    }
-    if (name == "quantizeToF16") {
-        return BuiltinType::kQuantizeToF16;
-    }
-    if (name == "radians") {
-        return BuiltinType::kRadians;
-    }
-    if (name == "reflect") {
-        return BuiltinType::kReflect;
-    }
-    if (name == "refract") {
-        return BuiltinType::kRefract;
-    }
-    if (name == "reverseBits") {
-        return BuiltinType::kReverseBits;
-    }
-    if (name == "round") {
-        return BuiltinType::kRound;
-    }
-    if (name == "saturate") {
-        return BuiltinType::kSaturate;
-    }
-    if (name == "select") {
-        return BuiltinType::kSelect;
-    }
-    if (name == "sign") {
-        return BuiltinType::kSign;
-    }
-    if (name == "sin") {
-        return BuiltinType::kSin;
-    }
-    if (name == "sinh") {
-        return BuiltinType::kSinh;
-    }
-    if (name == "smoothstep") {
-        return BuiltinType::kSmoothstep;
-    }
-    if (name == "sqrt") {
-        return BuiltinType::kSqrt;
-    }
-    if (name == "step") {
-        return BuiltinType::kStep;
-    }
-    if (name == "storageBarrier") {
-        return BuiltinType::kStorageBarrier;
-    }
-    if (name == "tan") {
-        return BuiltinType::kTan;
-    }
-    if (name == "tanh") {
-        return BuiltinType::kTanh;
-    }
-    if (name == "transpose") {
-        return BuiltinType::kTranspose;
-    }
-    if (name == "trunc") {
-        return BuiltinType::kTrunc;
-    }
-    if (name == "unpack2x16float") {
-        return BuiltinType::kUnpack2X16Float;
-    }
-    if (name == "unpack2x16snorm") {
-        return BuiltinType::kUnpack2X16Snorm;
-    }
-    if (name == "unpack2x16unorm") {
-        return BuiltinType::kUnpack2X16Unorm;
-    }
-    if (name == "unpack4x8snorm") {
-        return BuiltinType::kUnpack4X8Snorm;
-    }
-    if (name == "unpack4x8unorm") {
-        return BuiltinType::kUnpack4X8Unorm;
-    }
-    if (name == "workgroupBarrier") {
-        return BuiltinType::kWorkgroupBarrier;
-    }
-    if (name == "workgroupUniformLoad") {
-        return BuiltinType::kWorkgroupUniformLoad;
-    }
-    if (name == "textureDimensions") {
-        return BuiltinType::kTextureDimensions;
-    }
-    if (name == "textureGather") {
-        return BuiltinType::kTextureGather;
-    }
-    if (name == "textureGatherCompare") {
-        return BuiltinType::kTextureGatherCompare;
-    }
-    if (name == "textureNumLayers") {
-        return BuiltinType::kTextureNumLayers;
-    }
-    if (name == "textureNumLevels") {
-        return BuiltinType::kTextureNumLevels;
-    }
-    if (name == "textureNumSamples") {
-        return BuiltinType::kTextureNumSamples;
-    }
-    if (name == "textureSample") {
-        return BuiltinType::kTextureSample;
-    }
-    if (name == "textureSampleBias") {
-        return BuiltinType::kTextureSampleBias;
-    }
-    if (name == "textureSampleCompare") {
-        return BuiltinType::kTextureSampleCompare;
-    }
-    if (name == "textureSampleCompareLevel") {
-        return BuiltinType::kTextureSampleCompareLevel;
-    }
-    if (name == "textureSampleGrad") {
-        return BuiltinType::kTextureSampleGrad;
-    }
-    if (name == "textureSampleLevel") {
-        return BuiltinType::kTextureSampleLevel;
-    }
-    if (name == "textureSampleBaseClampToEdge") {
-        return BuiltinType::kTextureSampleBaseClampToEdge;
-    }
-    if (name == "textureStore") {
-        return BuiltinType::kTextureStore;
-    }
-    if (name == "textureLoad") {
-        return BuiltinType::kTextureLoad;
-    }
-    if (name == "atomicLoad") {
-        return BuiltinType::kAtomicLoad;
-    }
-    if (name == "atomicStore") {
-        return BuiltinType::kAtomicStore;
-    }
-    if (name == "atomicAdd") {
-        return BuiltinType::kAtomicAdd;
-    }
-    if (name == "atomicSub") {
-        return BuiltinType::kAtomicSub;
-    }
-    if (name == "atomicMax") {
-        return BuiltinType::kAtomicMax;
-    }
-    if (name == "atomicMin") {
-        return BuiltinType::kAtomicMin;
-    }
-    if (name == "atomicAnd") {
-        return BuiltinType::kAtomicAnd;
-    }
-    if (name == "atomicOr") {
-        return BuiltinType::kAtomicOr;
-    }
-    if (name == "atomicXor") {
-        return BuiltinType::kAtomicXor;
-    }
-    if (name == "atomicExchange") {
-        return BuiltinType::kAtomicExchange;
-    }
-    if (name == "atomicCompareExchangeWeak") {
-        return BuiltinType::kAtomicCompareExchangeWeak;
-    }
-    if (name == "_tint_materialize") {
-        return BuiltinType::kTintMaterialize;
-    }
-    return BuiltinType::kNone;
-}
-
-const char* str(BuiltinType i) {
-    switch (i) {
-        case BuiltinType::kNone:
-            return "<none>";
-        case BuiltinType::kAbs:
-            return "abs";
-        case BuiltinType::kAcos:
-            return "acos";
-        case BuiltinType::kAcosh:
-            return "acosh";
-        case BuiltinType::kAll:
-            return "all";
-        case BuiltinType::kAny:
-            return "any";
-        case BuiltinType::kArrayLength:
-            return "arrayLength";
-        case BuiltinType::kAsin:
-            return "asin";
-        case BuiltinType::kAsinh:
-            return "asinh";
-        case BuiltinType::kAtan:
-            return "atan";
-        case BuiltinType::kAtan2:
-            return "atan2";
-        case BuiltinType::kAtanh:
-            return "atanh";
-        case BuiltinType::kCeil:
-            return "ceil";
-        case BuiltinType::kClamp:
-            return "clamp";
-        case BuiltinType::kCos:
-            return "cos";
-        case BuiltinType::kCosh:
-            return "cosh";
-        case BuiltinType::kCountLeadingZeros:
-            return "countLeadingZeros";
-        case BuiltinType::kCountOneBits:
-            return "countOneBits";
-        case BuiltinType::kCountTrailingZeros:
-            return "countTrailingZeros";
-        case BuiltinType::kCross:
-            return "cross";
-        case BuiltinType::kDegrees:
-            return "degrees";
-        case BuiltinType::kDeterminant:
-            return "determinant";
-        case BuiltinType::kDistance:
-            return "distance";
-        case BuiltinType::kDot:
-            return "dot";
-        case BuiltinType::kDot4I8Packed:
-            return "dot4I8Packed";
-        case BuiltinType::kDot4U8Packed:
-            return "dot4U8Packed";
-        case BuiltinType::kDpdx:
-            return "dpdx";
-        case BuiltinType::kDpdxCoarse:
-            return "dpdxCoarse";
-        case BuiltinType::kDpdxFine:
-            return "dpdxFine";
-        case BuiltinType::kDpdy:
-            return "dpdy";
-        case BuiltinType::kDpdyCoarse:
-            return "dpdyCoarse";
-        case BuiltinType::kDpdyFine:
-            return "dpdyFine";
-        case BuiltinType::kExp:
-            return "exp";
-        case BuiltinType::kExp2:
-            return "exp2";
-        case BuiltinType::kExtractBits:
-            return "extractBits";
-        case BuiltinType::kFaceForward:
-            return "faceForward";
-        case BuiltinType::kFirstLeadingBit:
-            return "firstLeadingBit";
-        case BuiltinType::kFirstTrailingBit:
-            return "firstTrailingBit";
-        case BuiltinType::kFloor:
-            return "floor";
-        case BuiltinType::kFma:
-            return "fma";
-        case BuiltinType::kFract:
-            return "fract";
-        case BuiltinType::kFrexp:
-            return "frexp";
-        case BuiltinType::kFwidth:
-            return "fwidth";
-        case BuiltinType::kFwidthCoarse:
-            return "fwidthCoarse";
-        case BuiltinType::kFwidthFine:
-            return "fwidthFine";
-        case BuiltinType::kInsertBits:
-            return "insertBits";
-        case BuiltinType::kInverseSqrt:
-            return "inverseSqrt";
-        case BuiltinType::kLdexp:
-            return "ldexp";
-        case BuiltinType::kLength:
-            return "length";
-        case BuiltinType::kLog:
-            return "log";
-        case BuiltinType::kLog2:
-            return "log2";
-        case BuiltinType::kMax:
-            return "max";
-        case BuiltinType::kMin:
-            return "min";
-        case BuiltinType::kMix:
-            return "mix";
-        case BuiltinType::kModf:
-            return "modf";
-        case BuiltinType::kNormalize:
-            return "normalize";
-        case BuiltinType::kPack2X16Float:
-            return "pack2x16float";
-        case BuiltinType::kPack2X16Snorm:
-            return "pack2x16snorm";
-        case BuiltinType::kPack2X16Unorm:
-            return "pack2x16unorm";
-        case BuiltinType::kPack4X8Snorm:
-            return "pack4x8snorm";
-        case BuiltinType::kPack4X8Unorm:
-            return "pack4x8unorm";
-        case BuiltinType::kPow:
-            return "pow";
-        case BuiltinType::kQuantizeToF16:
-            return "quantizeToF16";
-        case BuiltinType::kRadians:
-            return "radians";
-        case BuiltinType::kReflect:
-            return "reflect";
-        case BuiltinType::kRefract:
-            return "refract";
-        case BuiltinType::kReverseBits:
-            return "reverseBits";
-        case BuiltinType::kRound:
-            return "round";
-        case BuiltinType::kSaturate:
-            return "saturate";
-        case BuiltinType::kSelect:
-            return "select";
-        case BuiltinType::kSign:
-            return "sign";
-        case BuiltinType::kSin:
-            return "sin";
-        case BuiltinType::kSinh:
-            return "sinh";
-        case BuiltinType::kSmoothstep:
-            return "smoothstep";
-        case BuiltinType::kSqrt:
-            return "sqrt";
-        case BuiltinType::kStep:
-            return "step";
-        case BuiltinType::kStorageBarrier:
-            return "storageBarrier";
-        case BuiltinType::kTan:
-            return "tan";
-        case BuiltinType::kTanh:
-            return "tanh";
-        case BuiltinType::kTranspose:
-            return "transpose";
-        case BuiltinType::kTrunc:
-            return "trunc";
-        case BuiltinType::kUnpack2X16Float:
-            return "unpack2x16float";
-        case BuiltinType::kUnpack2X16Snorm:
-            return "unpack2x16snorm";
-        case BuiltinType::kUnpack2X16Unorm:
-            return "unpack2x16unorm";
-        case BuiltinType::kUnpack4X8Snorm:
-            return "unpack4x8snorm";
-        case BuiltinType::kUnpack4X8Unorm:
-            return "unpack4x8unorm";
-        case BuiltinType::kWorkgroupBarrier:
-            return "workgroupBarrier";
-        case BuiltinType::kWorkgroupUniformLoad:
-            return "workgroupUniformLoad";
-        case BuiltinType::kTextureDimensions:
-            return "textureDimensions";
-        case BuiltinType::kTextureGather:
-            return "textureGather";
-        case BuiltinType::kTextureGatherCompare:
-            return "textureGatherCompare";
-        case BuiltinType::kTextureNumLayers:
-            return "textureNumLayers";
-        case BuiltinType::kTextureNumLevels:
-            return "textureNumLevels";
-        case BuiltinType::kTextureNumSamples:
-            return "textureNumSamples";
-        case BuiltinType::kTextureSample:
-            return "textureSample";
-        case BuiltinType::kTextureSampleBias:
-            return "textureSampleBias";
-        case BuiltinType::kTextureSampleCompare:
-            return "textureSampleCompare";
-        case BuiltinType::kTextureSampleCompareLevel:
-            return "textureSampleCompareLevel";
-        case BuiltinType::kTextureSampleGrad:
-            return "textureSampleGrad";
-        case BuiltinType::kTextureSampleLevel:
-            return "textureSampleLevel";
-        case BuiltinType::kTextureSampleBaseClampToEdge:
-            return "textureSampleBaseClampToEdge";
-        case BuiltinType::kTextureStore:
-            return "textureStore";
-        case BuiltinType::kTextureLoad:
-            return "textureLoad";
-        case BuiltinType::kAtomicLoad:
-            return "atomicLoad";
-        case BuiltinType::kAtomicStore:
-            return "atomicStore";
-        case BuiltinType::kAtomicAdd:
-            return "atomicAdd";
-        case BuiltinType::kAtomicSub:
-            return "atomicSub";
-        case BuiltinType::kAtomicMax:
-            return "atomicMax";
-        case BuiltinType::kAtomicMin:
-            return "atomicMin";
-        case BuiltinType::kAtomicAnd:
-            return "atomicAnd";
-        case BuiltinType::kAtomicOr:
-            return "atomicOr";
-        case BuiltinType::kAtomicXor:
-            return "atomicXor";
-        case BuiltinType::kAtomicExchange:
-            return "atomicExchange";
-        case BuiltinType::kAtomicCompareExchangeWeak:
-            return "atomicCompareExchangeWeak";
-        case BuiltinType::kTintMaterialize:
-            return "_tint_materialize";
-    }
-    return "<unknown>";
-}
-
-utils::StringStream& operator<<(utils::StringStream& out, BuiltinType i) {
-    out << str(i);
-    return out;
-}
-
-}  // namespace tint::sem
diff --git a/src/tint/sem/builtin_type.cc.tmpl b/src/tint/sem/builtin_type.cc.tmpl
deleted file mode 100644
index 86a8623..0000000
--- a/src/tint/sem/builtin_type.cc.tmpl
+++ /dev/null
@@ -1,44 +0,0 @@
-{{- /*
---------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate builtin_type.cc
-
-To update the generated file, run:
-    ./tools/run gen
-
-See:
-* tools/src/cmd/gen for structures used by this template
-* https://golang.org/pkg/text/template/ for documentation on the template syntax
---------------------------------------------------------------------------------
-*/ -}}
-
-#include "src/tint/sem/builtin_type.h"
-
-namespace tint::sem {
-
-BuiltinType ParseBuiltinType(const std::string& name) {
-{{- range Sem.Builtins  }}
-    if (name == "{{.Name}}") {
-        return BuiltinType::k{{PascalCase .Name}};
-    }
-{{- end  }}
-    return BuiltinType::kNone;
-}
-
-const char* str(BuiltinType i) {
-    switch (i) {
-        case BuiltinType::kNone:
-            return "<none>";
-{{- range Sem.Builtins  }}
-        case BuiltinType::k{{PascalCase .Name}}:
-            return "{{.Name}}";
-{{- end  }}
-    }
-    return "<unknown>";
-}
-
-utils::StringStream& operator<<(utils::StringStream& out, BuiltinType i) {
-    out << str(i);
-    return out;
-}
-
-}  // namespace tint::sem
diff --git a/src/tint/sem/builtin_type.h b/src/tint/sem/builtin_type.h
deleted file mode 100644
index 23f3749..0000000
--- a/src/tint/sem/builtin_type.h
+++ /dev/null
@@ -1,403 +0,0 @@
-// 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.
-
-////////////////////////////////////////////////////////////////////////////////
-// File generated by tools/src/cmd/gen
-// using the template:
-//   src/tint/sem/builtin_type.h.tmpl
-//
-// Do not modify this file directly
-////////////////////////////////////////////////////////////////////////////////
-
-#ifndef SRC_TINT_SEM_BUILTIN_TYPE_H_
-#define SRC_TINT_SEM_BUILTIN_TYPE_H_
-
-#include <string>
-
-#include "src/tint/utils/string_stream.h"
-
-namespace tint::sem {
-
-/// Enumerator of all builtin functions
-enum class BuiltinType {
-    kNone = -1,
-    kAbs,
-    kAcos,
-    kAcosh,
-    kAll,
-    kAny,
-    kArrayLength,
-    kAsin,
-    kAsinh,
-    kAtan,
-    kAtan2,
-    kAtanh,
-    kCeil,
-    kClamp,
-    kCos,
-    kCosh,
-    kCountLeadingZeros,
-    kCountOneBits,
-    kCountTrailingZeros,
-    kCross,
-    kDegrees,
-    kDeterminant,
-    kDistance,
-    kDot,
-    kDot4I8Packed,
-    kDot4U8Packed,
-    kDpdx,
-    kDpdxCoarse,
-    kDpdxFine,
-    kDpdy,
-    kDpdyCoarse,
-    kDpdyFine,
-    kExp,
-    kExp2,
-    kExtractBits,
-    kFaceForward,
-    kFirstLeadingBit,
-    kFirstTrailingBit,
-    kFloor,
-    kFma,
-    kFract,
-    kFrexp,
-    kFwidth,
-    kFwidthCoarse,
-    kFwidthFine,
-    kInsertBits,
-    kInverseSqrt,
-    kLdexp,
-    kLength,
-    kLog,
-    kLog2,
-    kMax,
-    kMin,
-    kMix,
-    kModf,
-    kNormalize,
-    kPack2X16Float,
-    kPack2X16Snorm,
-    kPack2X16Unorm,
-    kPack4X8Snorm,
-    kPack4X8Unorm,
-    kPow,
-    kQuantizeToF16,
-    kRadians,
-    kReflect,
-    kRefract,
-    kReverseBits,
-    kRound,
-    kSaturate,
-    kSelect,
-    kSign,
-    kSin,
-    kSinh,
-    kSmoothstep,
-    kSqrt,
-    kStep,
-    kStorageBarrier,
-    kTan,
-    kTanh,
-    kTranspose,
-    kTrunc,
-    kUnpack2X16Float,
-    kUnpack2X16Snorm,
-    kUnpack2X16Unorm,
-    kUnpack4X8Snorm,
-    kUnpack4X8Unorm,
-    kWorkgroupBarrier,
-    kWorkgroupUniformLoad,
-    kTextureDimensions,
-    kTextureGather,
-    kTextureGatherCompare,
-    kTextureNumLayers,
-    kTextureNumLevels,
-    kTextureNumSamples,
-    kTextureSample,
-    kTextureSampleBias,
-    kTextureSampleCompare,
-    kTextureSampleCompareLevel,
-    kTextureSampleGrad,
-    kTextureSampleLevel,
-    kTextureSampleBaseClampToEdge,
-    kTextureStore,
-    kTextureLoad,
-    kAtomicLoad,
-    kAtomicStore,
-    kAtomicAdd,
-    kAtomicSub,
-    kAtomicMax,
-    kAtomicMin,
-    kAtomicAnd,
-    kAtomicOr,
-    kAtomicXor,
-    kAtomicExchange,
-    kAtomicCompareExchangeWeak,
-    kTintMaterialize,
-};
-
-/// Matches the BuiltinType by name
-/// @param name the builtin name to parse
-/// @returns the parsed BuiltinType, or BuiltinType::kNone if `name` did not
-/// match any builtin.
-BuiltinType ParseBuiltinType(const std::string& name);
-
-/// @returns the name of the builtin function type. The spelling, including
-/// case, matches the name in the WGSL spec.
-const char* str(BuiltinType i);
-
-/// Emits the name of the builtin function type. The spelling, including case,
-/// matches the name in the WGSL spec.
-utils::StringStream& operator<<(utils::StringStream& out, BuiltinType i);
-
-/// All builtin function
-constexpr BuiltinType kBuiltinTypes[] = {
-    BuiltinType::kAbs,
-    BuiltinType::kAcos,
-    BuiltinType::kAcosh,
-    BuiltinType::kAll,
-    BuiltinType::kAny,
-    BuiltinType::kArrayLength,
-    BuiltinType::kAsin,
-    BuiltinType::kAsinh,
-    BuiltinType::kAtan,
-    BuiltinType::kAtan2,
-    BuiltinType::kAtanh,
-    BuiltinType::kCeil,
-    BuiltinType::kClamp,
-    BuiltinType::kCos,
-    BuiltinType::kCosh,
-    BuiltinType::kCountLeadingZeros,
-    BuiltinType::kCountOneBits,
-    BuiltinType::kCountTrailingZeros,
-    BuiltinType::kCross,
-    BuiltinType::kDegrees,
-    BuiltinType::kDeterminant,
-    BuiltinType::kDistance,
-    BuiltinType::kDot,
-    BuiltinType::kDot4I8Packed,
-    BuiltinType::kDot4U8Packed,
-    BuiltinType::kDpdx,
-    BuiltinType::kDpdxCoarse,
-    BuiltinType::kDpdxFine,
-    BuiltinType::kDpdy,
-    BuiltinType::kDpdyCoarse,
-    BuiltinType::kDpdyFine,
-    BuiltinType::kExp,
-    BuiltinType::kExp2,
-    BuiltinType::kExtractBits,
-    BuiltinType::kFaceForward,
-    BuiltinType::kFirstLeadingBit,
-    BuiltinType::kFirstTrailingBit,
-    BuiltinType::kFloor,
-    BuiltinType::kFma,
-    BuiltinType::kFract,
-    BuiltinType::kFrexp,
-    BuiltinType::kFwidth,
-    BuiltinType::kFwidthCoarse,
-    BuiltinType::kFwidthFine,
-    BuiltinType::kInsertBits,
-    BuiltinType::kInverseSqrt,
-    BuiltinType::kLdexp,
-    BuiltinType::kLength,
-    BuiltinType::kLog,
-    BuiltinType::kLog2,
-    BuiltinType::kMax,
-    BuiltinType::kMin,
-    BuiltinType::kMix,
-    BuiltinType::kModf,
-    BuiltinType::kNormalize,
-    BuiltinType::kPack2X16Float,
-    BuiltinType::kPack2X16Snorm,
-    BuiltinType::kPack2X16Unorm,
-    BuiltinType::kPack4X8Snorm,
-    BuiltinType::kPack4X8Unorm,
-    BuiltinType::kPow,
-    BuiltinType::kQuantizeToF16,
-    BuiltinType::kRadians,
-    BuiltinType::kReflect,
-    BuiltinType::kRefract,
-    BuiltinType::kReverseBits,
-    BuiltinType::kRound,
-    BuiltinType::kSaturate,
-    BuiltinType::kSelect,
-    BuiltinType::kSign,
-    BuiltinType::kSin,
-    BuiltinType::kSinh,
-    BuiltinType::kSmoothstep,
-    BuiltinType::kSqrt,
-    BuiltinType::kStep,
-    BuiltinType::kStorageBarrier,
-    BuiltinType::kTan,
-    BuiltinType::kTanh,
-    BuiltinType::kTranspose,
-    BuiltinType::kTrunc,
-    BuiltinType::kUnpack2X16Float,
-    BuiltinType::kUnpack2X16Snorm,
-    BuiltinType::kUnpack2X16Unorm,
-    BuiltinType::kUnpack4X8Snorm,
-    BuiltinType::kUnpack4X8Unorm,
-    BuiltinType::kWorkgroupBarrier,
-    BuiltinType::kWorkgroupUniformLoad,
-    BuiltinType::kTextureDimensions,
-    BuiltinType::kTextureGather,
-    BuiltinType::kTextureGatherCompare,
-    BuiltinType::kTextureNumLayers,
-    BuiltinType::kTextureNumLevels,
-    BuiltinType::kTextureNumSamples,
-    BuiltinType::kTextureSample,
-    BuiltinType::kTextureSampleBias,
-    BuiltinType::kTextureSampleCompare,
-    BuiltinType::kTextureSampleCompareLevel,
-    BuiltinType::kTextureSampleGrad,
-    BuiltinType::kTextureSampleLevel,
-    BuiltinType::kTextureSampleBaseClampToEdge,
-    BuiltinType::kTextureStore,
-    BuiltinType::kTextureLoad,
-    BuiltinType::kAtomicLoad,
-    BuiltinType::kAtomicStore,
-    BuiltinType::kAtomicAdd,
-    BuiltinType::kAtomicSub,
-    BuiltinType::kAtomicMax,
-    BuiltinType::kAtomicMin,
-    BuiltinType::kAtomicAnd,
-    BuiltinType::kAtomicOr,
-    BuiltinType::kAtomicXor,
-    BuiltinType::kAtomicExchange,
-    BuiltinType::kAtomicCompareExchangeWeak,
-    BuiltinType::kTintMaterialize,
-};
-
-/// All builtin function names
-constexpr const char* kBuiltinStrings[] = {
-    "abs",
-    "acos",
-    "acosh",
-    "all",
-    "any",
-    "arrayLength",
-    "asin",
-    "asinh",
-    "atan",
-    "atan2",
-    "atanh",
-    "ceil",
-    "clamp",
-    "cos",
-    "cosh",
-    "countLeadingZeros",
-    "countOneBits",
-    "countTrailingZeros",
-    "cross",
-    "degrees",
-    "determinant",
-    "distance",
-    "dot",
-    "dot4I8Packed",
-    "dot4U8Packed",
-    "dpdx",
-    "dpdxCoarse",
-    "dpdxFine",
-    "dpdy",
-    "dpdyCoarse",
-    "dpdyFine",
-    "exp",
-    "exp2",
-    "extractBits",
-    "faceForward",
-    "firstLeadingBit",
-    "firstTrailingBit",
-    "floor",
-    "fma",
-    "fract",
-    "frexp",
-    "fwidth",
-    "fwidthCoarse",
-    "fwidthFine",
-    "insertBits",
-    "inverseSqrt",
-    "ldexp",
-    "length",
-    "log",
-    "log2",
-    "max",
-    "min",
-    "mix",
-    "modf",
-    "normalize",
-    "pack2x16float",
-    "pack2x16snorm",
-    "pack2x16unorm",
-    "pack4x8snorm",
-    "pack4x8unorm",
-    "pow",
-    "quantizeToF16",
-    "radians",
-    "reflect",
-    "refract",
-    "reverseBits",
-    "round",
-    "saturate",
-    "select",
-    "sign",
-    "sin",
-    "sinh",
-    "smoothstep",
-    "sqrt",
-    "step",
-    "storageBarrier",
-    "tan",
-    "tanh",
-    "transpose",
-    "trunc",
-    "unpack2x16float",
-    "unpack2x16snorm",
-    "unpack2x16unorm",
-    "unpack4x8snorm",
-    "unpack4x8unorm",
-    "workgroupBarrier",
-    "workgroupUniformLoad",
-    "textureDimensions",
-    "textureGather",
-    "textureGatherCompare",
-    "textureNumLayers",
-    "textureNumLevels",
-    "textureNumSamples",
-    "textureSample",
-    "textureSampleBias",
-    "textureSampleCompare",
-    "textureSampleCompareLevel",
-    "textureSampleGrad",
-    "textureSampleLevel",
-    "textureSampleBaseClampToEdge",
-    "textureStore",
-    "textureLoad",
-    "atomicLoad",
-    "atomicStore",
-    "atomicAdd",
-    "atomicSub",
-    "atomicMax",
-    "atomicMin",
-    "atomicAnd",
-    "atomicOr",
-    "atomicXor",
-    "atomicExchange",
-    "atomicCompareExchangeWeak",
-    "_tint_materialize",
-};
-
-}  // namespace tint::sem
-
-#endif  // SRC_TINT_SEM_BUILTIN_TYPE_H_
diff --git a/src/tint/sem/builtin_type.h.tmpl b/src/tint/sem/builtin_type.h.tmpl
deleted file mode 100644
index 366db95..0000000
--- a/src/tint/sem/builtin_type.h.tmpl
+++ /dev/null
@@ -1,61 +0,0 @@
-{{- /*
---------------------------------------------------------------------------------
-Template file for use with tools/src/cmd/gen to generate builtin_type.h
-
-To update the generated file, run:
-    ./tools/run gen
-
-See:
-* tools/src/cmd/gen for structures used by this template
-* https://golang.org/pkg/text/template/ for documentation on the template syntax
---------------------------------------------------------------------------------
-*/ -}}
-
-#ifndef SRC_TINT_SEM_BUILTIN_TYPE_H_
-#define SRC_TINT_SEM_BUILTIN_TYPE_H_
-
-#include <string>
-
-#include "src/tint/utils/string_stream.h"
-
-namespace tint::sem {
-
-/// Enumerator of all builtin functions
-enum class BuiltinType {
-    kNone = -1,
-{{- range Sem.Builtins }}
-    k{{PascalCase .Name}},
-{{- end }}
-};
-
-/// Matches the BuiltinType by name
-/// @param name the builtin name to parse
-/// @returns the parsed BuiltinType, or BuiltinType::kNone if `name` did not
-/// match any builtin.
-BuiltinType ParseBuiltinType(const std::string& name);
-
-/// @returns the name of the builtin function type. The spelling, including
-/// case, matches the name in the WGSL spec.
-const char* str(BuiltinType i);
-
-/// Emits the name of the builtin function type. The spelling, including case,
-/// matches the name in the WGSL spec.
-utils::StringStream& operator<<(utils::StringStream& out, BuiltinType i);
-
-/// All builtin function
-constexpr BuiltinType kBuiltinTypes[] = {
-{{- range Sem.Builtins }}
-    BuiltinType::k{{PascalCase .Name}},
-{{- end }}
-};
-
-/// All builtin function names
-constexpr const char* kBuiltinStrings[] = {
-{{- range Sem.Builtins }}
-    "{{.Name}}",
-{{- end }}
-};
-
-}  // namespace tint::sem
-
-#endif  // SRC_TINT_SEM_BUILTIN_TYPE_H_
diff --git a/src/tint/sem/diagnostic_severity_test.cc b/src/tint/sem/diagnostic_severity_test.cc
index fa57b55..a22b43e 100644
--- a/src/tint/sem/diagnostic_severity_test.cc
+++ b/src/tint/sem/diagnostic_severity_test.cc
@@ -31,45 +31,121 @@
         // @diagnostic(off, chromium_unreachable_code)
         // fn foo() {
         //   @diagnostic(info, chromium_unreachable_code) {
+        //     @diagnostic(error, chromium_unreachable_code)
         //     if (true) @diagnostic(warning, chromium_unreachable_code) {
         //       return;
+        //     } else if (false) {
+        //       return;
+        //     } else @diagnostic(info, chromium_unreachable_code) {
+        //       return;
+        //     }
+        //     return;
+        //
+        //     @diagnostic(error, chromium_unreachable_code)
+        //     switch (42) {
+        //       case 0 @diagnostic(warning, chromium_unreachable_code) {
+        //         return;
+        //       }
+        //       default {
+        //         return;
+        //       }
+        //     }
+        //
+        //     @diagnostic(error, chromium_unreachable_code)
+        //     for (var i = 0; false; i++) @diagnostic(warning, chromium_unreachable_code) {
+        //       return;
+        //     }
+        //   }
+        //
+        //     @diagnostic(error, chromium_unreachable_code)
+        //     while (false) @diagnostic(warning, chromium_unreachable_code) {
+        //       return;
         //     }
         //   }
         // }
         //
         // fn bar() {
-        //   {
-        //     if (true) {
-        //       return;
-        //     }
-        //   }
+        //   return;
         // }
         auto rule = builtin::DiagnosticRule::kChromiumUnreachableCode;
         auto func_severity = builtin::DiagnosticSeverity::kOff;
         auto block_severity = builtin::DiagnosticSeverity::kInfo;
-        auto if_severity = builtin::DiagnosticSeverity::kInfo;
+        auto if_severity = builtin::DiagnosticSeverity::kError;
+        auto if_body_severity = builtin::DiagnosticSeverity::kWarning;
+        auto else_body_severity = builtin::DiagnosticSeverity::kInfo;
+        auto switch_severity = builtin::DiagnosticSeverity::kError;
+        auto case_severity = builtin::DiagnosticSeverity::kWarning;
+        auto for_severity = builtin::DiagnosticSeverity::kError;
+        auto for_body_severity = builtin::DiagnosticSeverity::kWarning;
+        auto while_severity = builtin::DiagnosticSeverity::kError;
+        auto while_body_severity = builtin::DiagnosticSeverity::kWarning;
         auto attr = [&](auto severity) {
             return utils::Vector{DiagnosticAttribute(severity, "chromium_unreachable_code")};
         };
 
-        auto* return_1 = Return();
-        auto* if_1 = If(Expr(true), Block(utils::Vector{return_1}, attr(if_severity)));
-        auto* block_1 = Block(utils::Vector{if_1}, attr(block_severity));
+        auto* return_foo_if = Return();
+        auto* return_foo_elseif = Return();
+        auto* return_foo_else = Return();
+        auto* return_foo_block = Return();
+        auto* return_foo_case = Return();
+        auto* return_foo_default = Return();
+        auto* return_foo_for = Return();
+        auto* return_foo_while = Return();
+        auto* else_stmt = Block(utils::Vector{return_foo_else}, attr(else_body_severity));
+        auto* elseif = If(Expr(false), Block(return_foo_elseif), Else(else_stmt));
+        auto* if_foo = If(Expr(true), Block(utils::Vector{return_foo_if}, attr(if_body_severity)),
+                          Else(elseif), attr(if_severity));
+        auto* case_stmt =
+            Case(CaseSelector(0_a), Block(utils::Vector{return_foo_case}, attr(case_severity)));
+        auto* swtch = Switch(42_a, utils::Vector{case_stmt, DefaultCase(Block(return_foo_default))},
+                             attr(switch_severity));
+        auto* fl =
+            For(Decl(Var("i", ty.i32())), false, Increment("i"),
+                Block(utils::Vector{return_foo_for}, attr(for_body_severity)), attr(for_severity));
+        auto* wl = While(false, Block(utils::Vector{return_foo_while}, attr(while_body_severity)),
+                         attr(while_severity));
+        auto* block_1 =
+            Block(utils::Vector{if_foo, return_foo_block, swtch, fl, wl}, attr(block_severity));
         auto* func_attr = DiagnosticAttribute(func_severity, "chromium_unreachable_code");
         auto* foo = Func("foo", {}, ty.void_(), utils::Vector{block_1}, utils::Vector{func_attr});
 
-        auto* return_2 = Return();
-        auto* bar = Func("bar", {}, ty.void_(), utils::Vector{return_2});
+        auto* return_bar = Return();
+        auto* bar = Func("bar", {}, ty.void_(), utils::Vector{return_bar});
 
         auto p = Build();
         EXPECT_TRUE(p.IsValid()) << p.Diagnostics().str();
 
         EXPECT_EQ(p.Sem().DiagnosticSeverity(foo, rule), func_severity);
         EXPECT_EQ(p.Sem().DiagnosticSeverity(block_1, rule), block_severity);
-        EXPECT_EQ(p.Sem().DiagnosticSeverity(if_1, rule), block_severity);
-        EXPECT_EQ(p.Sem().DiagnosticSeverity(return_1, rule), if_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(if_foo, rule), if_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(if_foo->condition, rule), if_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(if_foo->body, rule), if_body_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_if, rule), if_body_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(elseif, rule), if_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(elseif->condition, rule), if_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(elseif->body, rule), if_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_elseif, rule), if_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(else_stmt, rule), else_body_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_else, rule), else_body_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(swtch, rule), switch_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(swtch->condition, rule), switch_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(case_stmt, rule), switch_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(case_stmt->body, rule), case_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_case, rule), case_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_default, rule), switch_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(fl, rule), while_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(fl->initializer, rule), for_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(fl->condition, rule), for_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(fl->continuing, rule), for_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(fl->body, rule), for_body_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_for, rule), for_body_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(wl, rule), while_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(wl->condition, rule), while_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(wl->body, rule), while_body_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_while, rule), while_body_severity);
+
         EXPECT_EQ(p.Sem().DiagnosticSeverity(bar, rule), global_severity);
-        EXPECT_EQ(p.Sem().DiagnosticSeverity(return_2, rule), global_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(return_bar, rule), global_severity);
     }
 };
 
diff --git a/src/tint/sem/external_texture.h b/src/tint/sem/external_texture.h
new file mode 100644
index 0000000..2ad5673
--- /dev/null
+++ b/src/tint/sem/external_texture.h
@@ -0,0 +1,45 @@
+// Copyright 2023 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_TINT_SEM_EXTERNAL_TEXTURE_H_
+#define SRC_TINT_SEM_EXTERNAL_TEXTURE_H_
+
+#include <unordered_map>
+
+#include "src/tint/sem/binding_point.h"
+
+namespace tint::sem::external_texture {
+
+/// This struct identifies the binding groups and locations for new bindings to
+/// use when transforming a texture_external instance.
+struct BindingPoints {
+    /// The desired binding location of the texture_2d representing plane #1 when
+    /// a texture_external binding is expanded.
+    BindingPoint plane_1;
+    /// The desired binding location of the ExternalTextureParams uniform when a
+    /// texture_external binding is expanded.
+    BindingPoint params;
+
+    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
+    TINT_REFLECT(plane_1, params);
+};
+
+/// BindingsMap is a map where the key is the binding location of a
+/// texture_external and the value is a struct containing the desired
+/// locations for new bindings expanded from the texture_external instance.
+using BindingsMap = std::unordered_map<BindingPoint, BindingPoints>;
+
+}  // namespace tint::sem::external_texture
+
+#endif  // SRC_TINT_SEM_EXTERNAL_TEXTURE_H_
diff --git a/src/tint/sem/info.cc b/src/tint/sem/info.cc
index e124c1c..52a04b5 100644
--- a/src/tint/sem/info.cc
+++ b/src/tint/sem/info.cc
@@ -18,6 +18,7 @@
 #include "src/tint/sem/module.h"
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/value_expression.h"
+#include "src/tint/switch.h"
 
 namespace tint::sem {
 
diff --git a/src/tint/sem/value_expression.cc b/src/tint/sem/value_expression.cc
index 9fe4615..9aacfa3 100644
--- a/src/tint/sem/value_expression.cc
+++ b/src/tint/sem/value_expression.cc
@@ -18,6 +18,7 @@
 
 #include "src/tint/sem/load.h"
 #include "src/tint/sem/materialize.h"
+#include "src/tint/switch.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::sem::ValueExpression);
 
diff --git a/src/tint/sem/value_expression_test.cc b/src/tint/sem/value_expression_test.cc
index 1758894..0a2c9ef 100644
--- a/src/tint/sem/value_expression_test.cc
+++ b/src/tint/sem/value_expression_test.cc
@@ -29,9 +29,9 @@
     ~MockConstant() override {}
     const type::Type* Type() const override { return type; }
     const constant::Value* Index(size_t) const override { return {}; }
+    size_t NumElements() const override { return 0; }
     bool AllZero() const override { return {}; }
     bool AnyZero() const override { return {}; }
-    bool AllEqual() const override { return {}; }
     size_t Hash() const override { return 0; }
     MockConstant* Clone(constant::CloneContext&) const override { return nullptr; }
 
diff --git a/src/tint/switch.h b/src/tint/switch.h
new file mode 100644
index 0000000..41bb99a
--- /dev/null
+++ b/src/tint/switch.h
@@ -0,0 +1,262 @@
+// Copyright 2023 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_TINT_SWITCH_H_
+#define SRC_TINT_SWITCH_H_
+
+#include <tuple>
+#include <utility>
+
+#include "src/tint/castable.h"
+#include "src/tint/utils/bitcast.h"
+#include "src/tint/utils/defer.h"
+
+namespace tint {
+
+/// Default can be used as the default case for a Switch(), when all previous cases failed to match.
+///
+/// Example:
+/// ```
+/// Switch(object,
+///     [&](TypeA*) { /* ... */ },
+///     [&](TypeB*) { /* ... */ },
+///     [&](Default) { /* If not TypeA or TypeB */ });
+/// ```
+struct Default {};
+
+}  // namespace tint
+
+namespace tint::detail {
+
+/// Evaluates to the Switch case type being matched by the switch case function `FN`.
+/// @note does not handle the Default case
+/// @see Switch().
+template <typename FN>
+using SwitchCaseType = std::remove_pointer_t<traits::ParameterType<std::remove_reference_t<FN>, 0>>;
+
+/// Evaluates to true if the function `FN` has the signature of a Default case in a Switch().
+/// @see Switch().
+template <typename FN>
+inline constexpr bool IsDefaultCase =
+    std::is_same_v<traits::ParameterType<std::remove_reference_t<FN>, 0>, Default>;
+
+/// Searches the list of Switch cases for a Default case, returning the index of the Default case.
+/// If the a Default case is not found in the tuple, then -1 is returned.
+template <typename TUPLE, std::size_t START_IDX = 0>
+constexpr int IndexOfDefaultCase() {
+    if constexpr (START_IDX < std::tuple_size_v<TUPLE>) {
+        return IsDefaultCase<std::tuple_element_t<START_IDX, TUPLE>>
+                   ? static_cast<int>(START_IDX)
+                   : IndexOfDefaultCase<TUPLE, START_IDX + 1>();
+    } else {
+        return -1;
+    }
+}
+
+/// Resolves to T if T is not nullptr_t, otherwise resolves to Ignore.
+template <typename T>
+using NullptrToIgnore = std::conditional_t<std::is_same_v<T, std::nullptr_t>, Ignore, T>;
+
+/// Resolves to `const TYPE` if any of `CASE_RETURN_TYPES` are const or pointer-to-const, otherwise
+/// resolves to TYPE.
+template <typename TYPE, typename... CASE_RETURN_TYPES>
+using PropagateReturnConst = std::conditional_t<
+    // Are any of the pointer-stripped types const?
+    (std::is_const_v<std::remove_pointer_t<CASE_RETURN_TYPES>> || ...),
+    const TYPE,  // Yes: Apply const to TYPE
+    TYPE>;       // No:  Passthrough
+
+/// SwitchReturnTypeImpl is the implementation of SwitchReturnType
+template <bool IS_CASTABLE, typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
+struct SwitchReturnTypeImpl;
+
+/// SwitchReturnTypeImpl specialization for non-castable case types and an explicitly specified
+/// return type.
+template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
+struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, REQUESTED_TYPE, CASE_RETURN_TYPES...> {
+    /// Resolves to `REQUESTED_TYPE`
+    using type = REQUESTED_TYPE;
+};
+
+/// SwitchReturnTypeImpl specialization for non-castable case types and an inferred return type.
+template <typename... CASE_RETURN_TYPES>
+struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, Infer, CASE_RETURN_TYPES...> {
+    /// Resolves to the common type for all the cases return types.
+    using type = std::common_type_t<CASE_RETURN_TYPES...>;
+};
+
+/// SwitchReturnTypeImpl specialization for castable case types and an explicitly specified return
+/// type.
+template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
+struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, REQUESTED_TYPE, CASE_RETURN_TYPES...> {
+  public:
+    /// Resolves to `const REQUESTED_TYPE*` or `REQUESTED_TYPE*`
+    using type = PropagateReturnConst<std::remove_pointer_t<REQUESTED_TYPE>, CASE_RETURN_TYPES...>*;
+};
+
+/// SwitchReturnTypeImpl specialization for castable case types and an inferred return type.
+template <typename... CASE_RETURN_TYPES>
+struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, Infer, CASE_RETURN_TYPES...> {
+  private:
+    using InferredType =
+        CastableCommonBase<detail::NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>;
+
+  public:
+    /// `const T*` or `T*`, where T is the common base type for all the castable case types.
+    using type = PropagateReturnConst<InferredType, CASE_RETURN_TYPES...>*;
+};
+
+/// Resolves to the return type for a Switch() with the requested return type `REQUESTED_TYPE` and
+/// case statement return types. If `REQUESTED_TYPE` is Infer then the return type will be inferred
+/// from the case return types.
+template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
+using SwitchReturnType = typename SwitchReturnTypeImpl<
+    IsCastable<NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>,
+    REQUESTED_TYPE,
+    CASE_RETURN_TYPES...>::type;
+
+}  // namespace tint::detail
+
+namespace tint {
+
+/// Switch is used to dispatch one of the provided callback case handler functions based on the type
+/// of `object` and the parameter type of the case handlers. Switch will sequentially check the type
+/// of `object` against each of the switch case handler functions, and will invoke the first case
+/// handler function which has a parameter type that matches the object type. When a case handler is
+/// matched, it will be called with the single argument of `object` cast to the case handler's
+/// parameter type. Switch will invoke at most one case handler. Each of the case functions must
+/// have the signature `R(T*)` or `R(const T*)`, where `T` is the type matched by that case and `R`
+/// is the return type, consistent across all case handlers.
+///
+/// An optional default case function with the signature `R(Default)` can be used as the last case.
+/// This default case will be called if all previous cases failed to match.
+///
+/// If `object` is nullptr and a default case is provided, then the default case will be called. If
+/// `object` is nullptr and no default case is provided, then no cases will be called.
+///
+/// Example:
+/// ```
+/// Switch(object,
+///     [&](TypeA*) { /* ... */ },
+///     [&](TypeB*) { /* ... */ });
+///
+/// Switch(object,
+///     [&](TypeA*) { /* ... */ },
+///     [&](TypeB*) { /* ... */ },
+///     [&](Default) { /* Called if object is not TypeA or TypeB */ });
+/// ```
+///
+/// @param object the object who's type is used to
+/// @param cases the switch cases
+/// @return the value returned by the called case. If no cases matched, then the zero value for the
+/// consistent case type.
+template <typename RETURN_TYPE = detail::Infer, typename T = CastableBase, typename... CASES>
+inline auto Switch(T* object, CASES&&... cases) {
+    using ReturnType = detail::SwitchReturnType<RETURN_TYPE, traits::ReturnType<CASES>...>;
+    static constexpr int kDefaultIndex = detail::IndexOfDefaultCase<std::tuple<CASES...>>();
+    static constexpr bool kHasDefaultCase = kDefaultIndex >= 0;
+    static constexpr bool kHasReturnType = !std::is_same_v<ReturnType, void>;
+
+    // Static assertions
+    static constexpr bool kDefaultIsOK =
+        kDefaultIndex == -1 || kDefaultIndex == static_cast<int>(sizeof...(CASES) - 1);
+    static constexpr bool kReturnIsOK =
+        kHasDefaultCase || !kHasReturnType || std::is_constructible_v<ReturnType>;
+    static_assert(kDefaultIsOK, "Default case must be last in Switch()");
+    static_assert(kReturnIsOK,
+                  "Switch() requires either a Default case or a return type that is either void or "
+                  "default-constructable");
+
+    if (!object) {  // Object is nullptr, so no cases can match
+        if constexpr (kHasDefaultCase) {
+            // Evaluate default case.
+            auto&& default_case =
+                std::get<kDefaultIndex>(std::forward_as_tuple(std::forward<CASES>(cases)...));
+            return static_cast<ReturnType>(default_case(Default{}));
+        } else {
+            // No default case, no case can match.
+            if constexpr (kHasReturnType) {
+                return ReturnType{};
+            } else {
+                return;
+            }
+        }
+    }
+
+    // Replacement for std::aligned_storage as this is broken on earlier versions of MSVC.
+    using ReturnTypeOrU8 = std::conditional_t<kHasReturnType, ReturnType, uint8_t>;
+    struct alignas(alignof(ReturnTypeOrU8)) ReturnStorage {
+        uint8_t data[sizeof(ReturnTypeOrU8)];
+    };
+    ReturnStorage storage;
+    auto* result = utils::Bitcast<ReturnTypeOrU8*>(&storage);
+
+    const TypeInfo& type_info = object->TypeInfo();
+
+    // Examines the parameter type of the case function.
+    // If the parameter is a pointer type that `object` is of, or derives from, then that case
+    // function is called with `object` cast to that type, and `try_case` returns true.
+    // If the parameter is of type `Default`, then that case function is called and `try_case`
+    // returns true.
+    // Otherwise `try_case` returns false.
+    // If the case function is called and it returns a value, then this is copy constructed to the
+    // `result` pointer.
+    auto try_case = [&](auto&& case_fn) {
+        using CaseFunc = std::decay_t<decltype(case_fn)>;
+        using CaseType = detail::SwitchCaseType<CaseFunc>;
+        bool success = false;
+        if constexpr (std::is_same_v<CaseType, Default>) {
+            if constexpr (kHasReturnType) {
+                new (result) ReturnType(static_cast<ReturnType>(case_fn(Default{})));
+            } else {
+                case_fn(Default{});
+            }
+            success = true;
+        } else {
+            if (type_info.Is<CaseType>()) {
+                auto* v = static_cast<CaseType*>(object);
+                if constexpr (kHasReturnType) {
+                    new (result) ReturnType(static_cast<ReturnType>(case_fn(v)));
+                } else {
+                    case_fn(v);
+                }
+                success = true;
+            }
+        }
+        return success;
+    };
+
+    // Use a logical-or fold expression to try each of the cases in turn, until one matches the
+    // object type or a Default is reached. `handled` is true if a case function was called.
+    bool handled = ((try_case(std::forward<CASES>(cases)) || ...));
+
+    if constexpr (kHasReturnType) {
+        if constexpr (kHasDefaultCase) {
+            // Default case means there must be a returned value.
+            // No need to check handled, no requirement for a zero-initializer of ReturnType.
+            TINT_DEFER(result->~ReturnType());
+            return *result;
+        } else {
+            if (handled) {
+                TINT_DEFER(result->~ReturnType());
+                return *result;
+            }
+            return ReturnType{};
+        }
+    }
+}
+
+}  // namespace tint
+
+#endif  // SRC_TINT_SWITCH_H_
diff --git a/src/tint/castable_bench.cc b/src/tint/switch_bench.cc
similarity index 99%
rename from src/tint/castable_bench.cc
rename to src/tint/switch_bench.cc
index c9d0c43..ea6098b 100644
--- a/src/tint/castable_bench.cc
+++ b/src/tint/switch_bench.cc
@@ -16,7 +16,7 @@
 
 #include "benchmark/benchmark.h"
 
-#include "src/tint/castable.h"
+#include "src/tint/switch.h"
 
 namespace tint {
 namespace {
diff --git a/src/tint/switch_test.cc b/src/tint/switch_test.cc
new file mode 100644
index 0000000..d93f830
--- /dev/null
+++ b/src/tint/switch_test.cc
@@ -0,0 +1,552 @@
+// Copyright 2023 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/tint/switch.h"
+
+#include <memory>
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+struct Animal : public tint::Castable<Animal> {};
+struct Amphibian : public tint::Castable<Amphibian, Animal> {};
+struct Mammal : public tint::Castable<Mammal, Animal> {};
+struct Reptile : public tint::Castable<Reptile, Animal> {};
+struct Frog : public tint::Castable<Frog, Amphibian> {};
+struct Bear : public tint::Castable<Bear, Mammal> {};
+struct Lizard : public tint::Castable<Lizard, Reptile> {};
+struct Gecko : public tint::Castable<Gecko, Lizard> {};
+struct Iguana : public tint::Castable<Iguana, Lizard> {};
+
+TEST(Castable, SwitchNoDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        bool frog_matched_amphibian = false;
+        Switch(
+            frog.get(),  //
+            [&](Reptile*) { FAIL() << "frog is not reptile"; },
+            [&](Mammal*) { FAIL() << "frog is not mammal"; },
+            [&](Amphibian* amphibian) {
+                EXPECT_EQ(amphibian, frog.get());
+                frog_matched_amphibian = true;
+            });
+        EXPECT_TRUE(frog_matched_amphibian);
+    }
+    {
+        bool bear_matched_mammal = false;
+        Switch(
+            bear.get(),  //
+            [&](Reptile*) { FAIL() << "bear is not reptile"; },
+            [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
+            [&](Mammal* mammal) {
+                EXPECT_EQ(mammal, bear.get());
+                bear_matched_mammal = true;
+            });
+        EXPECT_TRUE(bear_matched_mammal);
+    }
+    {
+        bool gecko_matched_reptile = false;
+        Switch(
+            gecko.get(),  //
+            [&](Mammal*) { FAIL() << "gecko is not mammal"; },
+            [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
+            [&](Reptile* reptile) {
+                EXPECT_EQ(reptile, gecko.get());
+                gecko_matched_reptile = true;
+            });
+        EXPECT_TRUE(gecko_matched_reptile);
+    }
+}
+
+TEST(Castable, SwitchWithUnusedDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        bool frog_matched_amphibian = false;
+        Switch(
+            frog.get(),  //
+            [&](Reptile*) { FAIL() << "frog is not reptile"; },
+            [&](Mammal*) { FAIL() << "frog is not mammal"; },
+            [&](Amphibian* amphibian) {
+                EXPECT_EQ(amphibian, frog.get());
+                frog_matched_amphibian = true;
+            },
+            [&](Default) { FAIL() << "default should not have been selected"; });
+        EXPECT_TRUE(frog_matched_amphibian);
+    }
+    {
+        bool bear_matched_mammal = false;
+        Switch(
+            bear.get(),  //
+            [&](Reptile*) { FAIL() << "bear is not reptile"; },
+            [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
+            [&](Mammal* mammal) {
+                EXPECT_EQ(mammal, bear.get());
+                bear_matched_mammal = true;
+            },
+            [&](Default) { FAIL() << "default should not have been selected"; });
+        EXPECT_TRUE(bear_matched_mammal);
+    }
+    {
+        bool gecko_matched_reptile = false;
+        Switch(
+            gecko.get(),  //
+            [&](Mammal*) { FAIL() << "gecko is not mammal"; },
+            [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
+            [&](Reptile* reptile) {
+                EXPECT_EQ(reptile, gecko.get());
+                gecko_matched_reptile = true;
+            },
+            [&](Default) { FAIL() << "default should not have been selected"; });
+        EXPECT_TRUE(gecko_matched_reptile);
+    }
+}
+
+TEST(Castable, SwitchDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        bool frog_matched_default = false;
+        Switch(
+            frog.get(),  //
+            [&](Reptile*) { FAIL() << "frog is not reptile"; },
+            [&](Mammal*) { FAIL() << "frog is not mammal"; },
+            [&](Default) { frog_matched_default = true; });
+        EXPECT_TRUE(frog_matched_default);
+    }
+    {
+        bool bear_matched_default = false;
+        Switch(
+            bear.get(),  //
+            [&](Reptile*) { FAIL() << "bear is not reptile"; },
+            [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
+            [&](Default) { bear_matched_default = true; });
+        EXPECT_TRUE(bear_matched_default);
+    }
+    {
+        bool gecko_matched_default = false;
+        Switch(
+            gecko.get(),  //
+            [&](Mammal*) { FAIL() << "gecko is not mammal"; },
+            [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
+            [&](Default) { gecko_matched_default = true; });
+        EXPECT_TRUE(gecko_matched_default);
+    }
+}
+
+TEST(Castable, SwitchMatchFirst) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    {
+        bool frog_matched_animal = false;
+        Switch(
+            frog.get(),
+            [&](Animal* animal) {
+                EXPECT_EQ(animal, frog.get());
+                frog_matched_animal = true;
+            },
+            [&](Amphibian*) { FAIL() << "animal should have been matched first"; });
+        EXPECT_TRUE(frog_matched_animal);
+    }
+    {
+        bool frog_matched_amphibian = false;
+        Switch(
+            frog.get(),
+            [&](Amphibian* amphibain) {
+                EXPECT_EQ(amphibain, frog.get());
+                frog_matched_amphibian = true;
+            },
+            [&](Animal*) { FAIL() << "amphibian should have been matched first"; });
+        EXPECT_TRUE(frog_matched_amphibian);
+    }
+}
+
+TEST(Castable, SwitchReturnValueWithDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        const char* result = Switch(
+            frog.get(),                              //
+            [](Mammal*) { return "mammal"; },        //
+            [](Amphibian*) { return "amphibian"; },  //
+            [](Default) { return "unknown"; });
+        static_assert(std::is_same_v<decltype(result), const char*>);
+        EXPECT_EQ(std::string(result), "amphibian");
+    }
+    {
+        const char* result = Switch(
+            bear.get(),                              //
+            [](Mammal*) { return "mammal"; },        //
+            [](Amphibian*) { return "amphibian"; },  //
+            [](Default) { return "unknown"; });
+        static_assert(std::is_same_v<decltype(result), const char*>);
+        EXPECT_EQ(std::string(result), "mammal");
+    }
+    {
+        const char* result = Switch(
+            gecko.get(),                             //
+            [](Mammal*) { return "mammal"; },        //
+            [](Amphibian*) { return "amphibian"; },  //
+            [](Default) { return "unknown"; });
+        static_assert(std::is_same_v<decltype(result), const char*>);
+        EXPECT_EQ(std::string(result), "unknown");
+    }
+}
+
+TEST(Castable, SwitchReturnValueWithoutDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        const char* result = Switch(
+            frog.get(),                        //
+            [](Mammal*) { return "mammal"; },  //
+            [](Amphibian*) { return "amphibian"; });
+        static_assert(std::is_same_v<decltype(result), const char*>);
+        EXPECT_EQ(std::string(result), "amphibian");
+    }
+    {
+        const char* result = Switch(
+            bear.get(),                        //
+            [](Mammal*) { return "mammal"; },  //
+            [](Amphibian*) { return "amphibian"; });
+        static_assert(std::is_same_v<decltype(result), const char*>);
+        EXPECT_EQ(std::string(result), "mammal");
+    }
+    {
+        auto* result = Switch(
+            gecko.get(),                       //
+            [](Mammal*) { return "mammal"; },  //
+            [](Amphibian*) { return "amphibian"; });
+        static_assert(std::is_same_v<decltype(result), const char*>);
+        EXPECT_EQ(result, nullptr);
+    }
+}
+
+TEST(Castable, SwitchInferPODReturnTypeWithDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        auto result = Switch(
+            frog.get(),                       //
+            [](Mammal*) { return 1; },        //
+            [](Amphibian*) { return 2.0f; },  //
+            [](Default) { return 3.0; });
+        static_assert(std::is_same_v<decltype(result), double>);
+        EXPECT_EQ(result, 2.0);
+    }
+    {
+        auto result = Switch(
+            bear.get(),                       //
+            [](Mammal*) { return 1.0; },      //
+            [](Amphibian*) { return 2.0f; },  //
+            [](Default) { return 3; });
+        static_assert(std::is_same_v<decltype(result), double>);
+        EXPECT_EQ(result, 1.0);
+    }
+    {
+        auto result = Switch(
+            gecko.get(),                   //
+            [](Mammal*) { return 1.0f; },  //
+            [](Amphibian*) { return 2; },  //
+            [](Default) { return 3.0; });
+        static_assert(std::is_same_v<decltype(result), double>);
+        EXPECT_EQ(result, 3.0);
+    }
+}
+
+TEST(Castable, SwitchInferPODReturnTypeWithoutDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        auto result = Switch(
+            frog.get(),                 //
+            [](Mammal*) { return 1; },  //
+            [](Amphibian*) { return 2.0f; });
+        static_assert(std::is_same_v<decltype(result), float>);
+        EXPECT_EQ(result, 2.0f);
+    }
+    {
+        auto result = Switch(
+            bear.get(),                    //
+            [](Mammal*) { return 1.0f; },  //
+            [](Amphibian*) { return 2; });
+        static_assert(std::is_same_v<decltype(result), float>);
+        EXPECT_EQ(result, 1.0f);
+    }
+    {
+        auto result = Switch(
+            gecko.get(),                  //
+            [](Mammal*) { return 1.0; },  //
+            [](Amphibian*) { return 2.0f; });
+        static_assert(std::is_same_v<decltype(result), double>);
+        EXPECT_EQ(result, 0.0);
+    }
+}
+
+TEST(Castable, SwitchInferCastableReturnTypeWithDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        auto* result = Switch(
+            frog.get(),                          //
+            [](Mammal* p) { return p; },         //
+            [](Amphibian*) { return nullptr; },  //
+            [](Default) { return nullptr; });
+        static_assert(std::is_same_v<decltype(result), Mammal*>);
+        EXPECT_EQ(result, nullptr);
+    }
+    {
+        auto* result = Switch(
+            bear.get(),                   //
+            [](Mammal* p) { return p; },  //
+            [](Amphibian* p) { return const_cast<const Amphibian*>(p); },
+            [](Default) { return nullptr; });
+        static_assert(std::is_same_v<decltype(result), const Animal*>);
+        EXPECT_EQ(result, bear.get());
+    }
+    {
+        auto* result = Switch(
+            gecko.get(),                     //
+            [](Mammal* p) { return p; },     //
+            [](Amphibian* p) { return p; },  //
+            [](Default) -> CastableBase* { return nullptr; });
+        static_assert(std::is_same_v<decltype(result), CastableBase*>);
+        EXPECT_EQ(result, nullptr);
+    }
+}
+
+TEST(Castable, SwitchInferCastableReturnTypeWithoutDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        auto* result = Switch(
+            frog.get(),                   //
+            [](Mammal* p) { return p; },  //
+            [](Amphibian*) { return nullptr; });
+        static_assert(std::is_same_v<decltype(result), Mammal*>);
+        EXPECT_EQ(result, nullptr);
+    }
+    {
+        auto* result = Switch(
+            bear.get(),                                                     //
+            [](Mammal* p) { return p; },                                    //
+            [](Amphibian* p) { return const_cast<const Amphibian*>(p); });  //
+        static_assert(std::is_same_v<decltype(result), const Animal*>);
+        EXPECT_EQ(result, bear.get());
+    }
+    {
+        auto* result = Switch(
+            gecko.get(),                  //
+            [](Mammal* p) { return p; },  //
+            [](Amphibian* p) { return p; });
+        static_assert(std::is_same_v<decltype(result), Animal*>);
+        EXPECT_EQ(result, nullptr);
+    }
+}
+
+TEST(Castable, SwitchExplicitPODReturnTypeWithDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        auto result = Switch<double>(
+            frog.get(),                       //
+            [](Mammal*) { return 1; },        //
+            [](Amphibian*) { return 2.0f; },  //
+            [](Default) { return 3.0; });
+        static_assert(std::is_same_v<decltype(result), double>);
+        EXPECT_EQ(result, 2.0f);
+    }
+    {
+        auto result = Switch<double>(
+            bear.get(),                    //
+            [](Mammal*) { return 1; },     //
+            [](Amphibian*) { return 2; },  //
+            [](Default) { return 3; });
+        static_assert(std::is_same_v<decltype(result), double>);
+        EXPECT_EQ(result, 1.0f);
+    }
+    {
+        auto result = Switch<double>(
+            gecko.get(),                      //
+            [](Mammal*) { return 1.0f; },     //
+            [](Amphibian*) { return 2.0f; },  //
+            [](Default) { return 3.0f; });
+        static_assert(std::is_same_v<decltype(result), double>);
+        EXPECT_EQ(result, 3.0f);
+    }
+}
+
+TEST(Castable, SwitchExplicitPODReturnTypeWithoutDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        auto result = Switch<double>(
+            frog.get(),                 //
+            [](Mammal*) { return 1; },  //
+            [](Amphibian*) { return 2.0f; });
+        static_assert(std::is_same_v<decltype(result), double>);
+        EXPECT_EQ(result, 2.0f);
+    }
+    {
+        auto result = Switch<double>(
+            bear.get(),                    //
+            [](Mammal*) { return 1.0f; },  //
+            [](Amphibian*) { return 2; });
+        static_assert(std::is_same_v<decltype(result), double>);
+        EXPECT_EQ(result, 1.0f);
+    }
+    {
+        auto result = Switch<double>(
+            gecko.get(),                  //
+            [](Mammal*) { return 1.0; },  //
+            [](Amphibian*) { return 2.0f; });
+        static_assert(std::is_same_v<decltype(result), double>);
+        EXPECT_EQ(result, 0.0);
+    }
+}
+
+TEST(Castable, SwitchExplicitCastableReturnTypeWithDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        auto* result = Switch<Animal>(
+            frog.get(),                          //
+            [](Mammal* p) { return p; },         //
+            [](Amphibian*) { return nullptr; },  //
+            [](Default) { return nullptr; });
+        static_assert(std::is_same_v<decltype(result), Animal*>);
+        EXPECT_EQ(result, nullptr);
+    }
+    {
+        auto* result = Switch<CastableBase>(
+            bear.get(),                   //
+            [](Mammal* p) { return p; },  //
+            [](Amphibian* p) { return const_cast<const Amphibian*>(p); },
+            [](Default) { return nullptr; });
+        static_assert(std::is_same_v<decltype(result), const CastableBase*>);
+        EXPECT_EQ(result, bear.get());
+    }
+    {
+        auto* result = Switch<const Animal>(
+            gecko.get(),                     //
+            [](Mammal* p) { return p; },     //
+            [](Amphibian* p) { return p; },  //
+            [](Default) { return nullptr; });
+        static_assert(std::is_same_v<decltype(result), const Animal*>);
+        EXPECT_EQ(result, nullptr);
+    }
+}
+
+TEST(Castable, SwitchExplicitCastableReturnTypeWithoutDefault) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        auto* result = Switch<Animal>(
+            frog.get(),                   //
+            [](Mammal* p) { return p; },  //
+            [](Amphibian*) { return nullptr; });
+        static_assert(std::is_same_v<decltype(result), Animal*>);
+        EXPECT_EQ(result, nullptr);
+    }
+    {
+        auto* result = Switch<CastableBase>(
+            bear.get(),                                                     //
+            [](Mammal* p) { return p; },                                    //
+            [](Amphibian* p) { return const_cast<const Amphibian*>(p); });  //
+        static_assert(std::is_same_v<decltype(result), const CastableBase*>);
+        EXPECT_EQ(result, bear.get());
+    }
+    {
+        auto* result = Switch<const Animal*>(
+            gecko.get(),                  //
+            [](Mammal* p) { return p; },  //
+            [](Amphibian* p) { return p; });
+        static_assert(std::is_same_v<decltype(result), const Animal*>);
+        EXPECT_EQ(result, nullptr);
+    }
+}
+
+TEST(Castable, SwitchNull) {
+    Animal* null = nullptr;
+    Switch(
+        null,  //
+        [&](Amphibian*) { FAIL() << "should not be called"; },
+        [&](Animal*) { FAIL() << "should not be called"; });
+}
+
+TEST(Castable, SwitchNullNoDefault) {
+    Animal* null = nullptr;
+    bool default_called = false;
+    Switch(
+        null,  //
+        [&](Amphibian*) { FAIL() << "should not be called"; },
+        [&](Animal*) { FAIL() << "should not be called"; },
+        [&](Default) { default_called = true; });
+    EXPECT_TRUE(default_called);
+}
+
+TEST(Castable, SwitchReturnNoDefaultInitializer) {
+    struct Object {
+        explicit Object(int v) : value(v) {}
+        int value;
+    };
+
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    {
+        auto result = Switch(
+            frog.get(),                            //
+            [](Mammal*) { return Object(1); },     //
+            [](Amphibian*) { return Object(2); },  //
+            [](Default) { return Object(3); });
+        static_assert(std::is_same_v<decltype(result), Object>);
+        EXPECT_EQ(result.value, 2);
+    }
+    {
+        auto result = Switch(
+            frog.get(),                         //
+            [](Mammal*) { return Object(1); },  //
+            [](Default) { return Object(3); });
+        static_assert(std::is_same_v<decltype(result), Object>);
+        EXPECT_EQ(result.value, 3);
+    }
+}
+
+}  // namespace
+
+TINT_INSTANTIATE_TYPEINFO(Animal);
+TINT_INSTANTIATE_TYPEINFO(Amphibian);
+TINT_INSTANTIATE_TYPEINFO(Mammal);
+TINT_INSTANTIATE_TYPEINFO(Reptile);
+TINT_INSTANTIATE_TYPEINFO(Frog);
+TINT_INSTANTIATE_TYPEINFO(Bear);
+TINT_INSTANTIATE_TYPEINFO(Lizard);
+TINT_INSTANTIATE_TYPEINFO(Gecko);
+
+}  // namespace tint
diff --git a/src/tint/transform/array_length_from_uniform.cc b/src/tint/transform/array_length_from_uniform.cc
index 78535f1..a396965 100644
--- a/src/tint/transform/array_length_from_uniform.cc
+++ b/src/tint/transform/array_length_from_uniform.cc
@@ -37,7 +37,7 @@
     for (auto* fn : program->AST().Functions()) {
         if (auto* sem_fn = program->Sem().Get(fn)) {
             for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
-                if (builtin->Type() == sem::BuiltinType::kArrayLength) {
+                if (builtin->Type() == builtin::Function::kArrayLength) {
                     return true;
                 }
             }
@@ -199,7 +199,7 @@
 
             auto* call = sem.Get(call_expr)->UnwrapMaterialize()->As<sem::Call>();
             auto* builtin = call->Target()->As<sem::Builtin>();
-            if (!builtin || builtin->Type() != sem::BuiltinType::kArrayLength) {
+            if (!builtin || builtin->Type() != builtin::Function::kArrayLength) {
                 continue;
             }
 
diff --git a/src/tint/transform/builtin_polyfill.cc b/src/tint/transform/builtin_polyfill.cc
index 259eb11..cc770ab 100644
--- a/src/tint/transform/builtin_polyfill.cc
+++ b/src/tint/transform/builtin_polyfill.cc
@@ -23,6 +23,8 @@
 #include "src/tint/sem/builtin.h"
 #include "src/tint/sem/call.h"
 #include "src/tint/sem/type_expression.h"
+#include "src/tint/sem/value_conversion.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/storage_texture.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/utils/map.h"
@@ -40,17 +42,123 @@
 /// PIMPL state for the transform
 struct BuiltinPolyfill::State {
     /// Constructor
-    /// @param c the CloneContext
-    /// @param p the builtins to polyfill
-    State(CloneContext& c, Builtins p) : ctx(c), polyfill(p) {
+    /// @param program the source program
+    /// @param config the transform config
+    State(const Program* program, const Config& config) : src(program), cfg(config) {
         has_full_ptr_params = false;
-        for (auto* enable : c.src->AST().Enables()) {
-            if (enable->extension == builtin::Extension::kChromiumExperimentalFullPtrParameters) {
+        for (auto* enable : src->AST().Enables()) {
+            if (enable->HasExtension(builtin::Extension::kChromiumExperimentalFullPtrParameters)) {
                 has_full_ptr_params = true;
+                break;
             }
         }
     }
 
+    /// Runs the transform
+    /// @returns the new program or SkipTransform if the transform is not required
+    Transform::ApplyResult Run() {
+        for (auto* node : src->ASTNodes().Objects()) {
+            Switch(
+                node,  //
+                [&](const ast::CallExpression* expr) { Call(expr); },
+                [&](const ast::BinaryExpression* bin_op) {
+                    if (auto* s = src->Sem().Get(bin_op);
+                        !s || s->Stage() == sem::EvaluationStage::kConstant ||
+                        s->Stage() == sem::EvaluationStage::kNotEvaluated) {
+                        return;  // Don't polyfill @const expressions
+                    }
+                    switch (bin_op->op) {
+                        case ast::BinaryOp::kShiftLeft:
+                        case ast::BinaryOp::kShiftRight: {
+                            if (cfg.builtins.bitshift_modulo) {
+                                ctx.Replace(bin_op,
+                                            [this, bin_op] { return BitshiftModulo(bin_op); });
+                                made_changes = true;
+                            }
+                            break;
+                        }
+                        case ast::BinaryOp::kDivide: {
+                            if (cfg.builtins.int_div_mod) {
+                                auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
+                                if (lhs_ty->is_integer_scalar_or_vector()) {
+                                    ctx.Replace(bin_op,
+                                                [this, bin_op] { return IntDivMod(bin_op); });
+                                    made_changes = true;
+                                }
+                            }
+                            break;
+                        }
+                        case ast::BinaryOp::kModulo: {
+                            if (cfg.builtins.int_div_mod) {
+                                auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
+                                if (lhs_ty->is_integer_scalar_or_vector()) {
+                                    ctx.Replace(bin_op,
+                                                [this, bin_op] { return IntDivMod(bin_op); });
+                                    made_changes = true;
+                                }
+                            }
+                            if (cfg.builtins.precise_float_mod) {
+                                auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
+                                if (lhs_ty->is_float_scalar_or_vector()) {
+                                    ctx.Replace(bin_op,
+                                                [this, bin_op] { return PreciseFloatMod(bin_op); });
+                                    made_changes = true;
+                                }
+                            }
+                            break;
+                        }
+                        default:
+                            break;
+                    }
+                },
+                [&](const ast::Expression* expr) {
+                    if (cfg.builtins.bgra8unorm) {
+                        if (auto* ty_expr = src->Sem().Get<sem::TypeExpression>(expr)) {
+                            if (auto* tex = ty_expr->Type()->As<type::StorageTexture>()) {
+                                if (tex->texel_format() == builtin::TexelFormat::kBgra8Unorm) {
+                                    ctx.Replace(expr, [this, tex] {
+                                        return ctx.dst->Expr(ctx.dst->ty.storage_texture(
+                                            tex->dim(), builtin::TexelFormat::kRgba8Unorm,
+                                            tex->access()));
+                                    });
+                                    made_changes = true;
+                                }
+                            }
+                        }
+                    }
+                });
+        }
+
+        if (!made_changes) {
+            return SkipTransform;
+        }
+
+        ctx.Clone();
+        return Program(std::move(b));
+    }
+
+  private:
+    /// The source program
+    Program const* const src;
+    /// The transform config
+    const Config& cfg;
+    /// The destination program builder
+    ProgramBuilder b;
+    /// The clone context
+    CloneContext ctx{&b, src};
+    /// The source clone context
+    const sem::Info& sem = src->Sem();
+    /// Polyfill functions for binary operators.
+    utils::Hashmap<BinaryOpSignature, Symbol, 8> binary_op_polyfills;
+    /// Polyfill builtins.
+    utils::Hashmap<const sem::Builtin*, Symbol, 8> builtin_polyfills;
+    /// Polyfill f32 conversion to i32 or u32 (or vectors of)
+    utils::Hashmap<const type::Type*, Symbol, 2> f32_conv_polyfills;
+    // Tracks whether the chromium_experimental_full_ptr_parameters extension has been enabled.
+    bool has_full_ptr_params = false;
+    /// True if the transform has made changes (i.e. the program needs cloning)
+    bool made_changes = false;
+
     ////////////////////////////////////////////////////////////////////////////
     // Function polyfills
     ////////////////////////////////////////////////////////////////////////////
@@ -71,7 +179,7 @@
         };
 
         utils::Vector<const ast::Statement*, 4> body;
-        switch (polyfill.acosh) {
+        switch (cfg.builtins.acosh) {
             case Level::kFull:
                 // return log(x + sqrt(x*x - 1));
                 body.Push(b.Return(
@@ -85,7 +193,7 @@
             }
             default:
                 TINT_ICE(Transform, b.Diagnostics())
-                    << "unhandled polyfill level: " << static_cast<int>(polyfill.acosh);
+                    << "unhandled polyfill level: " << static_cast<int>(cfg.builtins.acosh);
                 return {};
         }
 
@@ -125,7 +233,7 @@
         };
 
         utils::Vector<const ast::Statement*, 1> body;
-        switch (polyfill.atanh) {
+        switch (cfg.builtins.atanh) {
             case Level::kFull:
                 // return log((1+x) / (1-x)) * 0.5
                 body.Push(
@@ -138,7 +246,7 @@
                 break;
             default:
                 TINT_ICE(Transform, b.Diagnostics())
-                    << "unhandled polyfill level: " << static_cast<int>(polyfill.acosh);
+                    << "unhandled polyfill level: " << static_cast<int>(cfg.builtins.acosh);
                 return {};
         }
 
@@ -306,7 +414,7 @@
             b.Decl(b.Let("e", b.Call("min", u32(W), b.Add("s", "count")))),
         };
 
-        switch (polyfill.extract_bits) {
+        switch (cfg.builtins.extract_bits) {
             case Level::kFull:
                 body.Push(b.Decl(b.Let("shl", b.Sub(u32(W), "e"))));
                 body.Push(b.Decl(b.Let("shr", b.Add("shl", "s"))));
@@ -328,7 +436,7 @@
                 break;
             default:
                 TINT_ICE(Transform, b.Diagnostics())
-                    << "unhandled polyfill level: " << static_cast<int>(polyfill.extract_bits);
+                    << "unhandled polyfill level: " << static_cast<int>(cfg.builtins.extract_bits);
                 return {};
         }
 
@@ -532,7 +640,7 @@
 
         utils::Vector<const ast::Statement*, 8> body;
 
-        switch (polyfill.insert_bits) {
+        switch (cfg.builtins.insert_bits) {
             case Level::kFull:
                 // let e = offset + count;
                 body.Push(b.Decl(b.Let("e", b.Add("offset", "count"))));
@@ -566,7 +674,7 @@
                 break;
             default:
                 TINT_ICE(Transform, b.Diagnostics())
-                    << "unhandled polyfill level: " << static_cast<int>(polyfill.insert_bits);
+                    << "unhandled polyfill level: " << static_cast<int>(cfg.builtins.insert_bits);
                 return {};
         }
 
@@ -718,6 +826,52 @@
         return name;
     }
 
+    /// Builds the polyfill function to value convert a scalar or vector of f32 to an i32 or u32 (or
+    /// vector of).
+    /// @param source the type of the value being converted
+    /// @param target the target conversion type
+    /// @return the polyfill function name
+    Symbol ConvF32ToIU32(const type::Type* source, const type::Type* target) {
+        struct Limits {
+            AFloat low_condition;
+            AInt low_limit;
+            AFloat high_condition;
+            AInt high_limit;
+        };
+        const bool is_signed = target->is_signed_integer_scalar_or_vector();
+        const Limits limits = is_signed ? Limits{
+                                              /* low_condition   */ -AFloat(0x80000000),
+                                              /* low_limit  */ -AInt(0x80000000),
+                                              /* high_condition  */ AFloat(0x7fffff80),
+                                              /* high_limit */ AInt(0x7fffffff),
+                                          }
+                                        : Limits{
+                                              /* low_condition   */ AFloat(0),
+                                              /* low_limit  */ AInt(0),
+                                              /* high_condition  */ AFloat(0xffffff00),
+                                              /* high_limit */ AInt(0xffffffff),
+                                          };
+
+        const uint32_t width = WidthOf(target);
+
+        // select(target(v), low_limit, v < low_condition)
+        auto* select_low = b.Call(builtin::Function::kSelect,               //
+                                  b.Call(T(target), "v"),                   //
+                                  ScalarOrVector(width, limits.low_limit),  //
+                                  b.LessThan("v", ScalarOrVector(width, limits.low_condition)));
+
+        // select(high_limit, select_low, v < high_condition)
+        auto* select_high = b.Call(builtin::Function::kSelect,                //
+                                   ScalarOrVector(width, limits.high_limit),  //
+                                   select_low,                                //
+                                   b.LessThan("v", ScalarOrVector(width, limits.high_condition)));
+
+        auto name = b.Symbols().New(is_signed ? "tint_ftoi" : "tint_ftou");
+        b.Func(name, utils::Vector{b.Param("v", T(source))}, T(target),
+               utils::Vector{b.Return(select_high)});
+        return name;
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Inline polyfills
     ////////////////////////////////////////////////////////////////////////////
@@ -727,8 +881,8 @@
     /// @param bin_op the original BinaryExpression
     /// @return the polyfill value for bitshift operation
     const ast::Expression* BitshiftModulo(const ast::BinaryExpression* bin_op) {
-        auto* lhs_ty = ctx.src->TypeOf(bin_op->lhs)->UnwrapRef();
-        auto* rhs_ty = ctx.src->TypeOf(bin_op->rhs)->UnwrapRef();
+        auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
+        auto* rhs_ty = src->TypeOf(bin_op->rhs)->UnwrapRef();
         auto* lhs_el_ty = type::Type::DeepestElementOf(lhs_ty);
         const ast::Expression* mask = b.Expr(AInt(lhs_el_ty->Size() * 8 - 1));
         if (rhs_ty->Is<type::Vector>()) {
@@ -744,8 +898,8 @@
     /// @param bin_op the original BinaryExpression
     /// @return the polyfill divide or modulo
     const ast::Expression* IntDivMod(const ast::BinaryExpression* bin_op) {
-        auto* lhs_ty = ctx.src->TypeOf(bin_op->lhs)->UnwrapRef();
-        auto* rhs_ty = ctx.src->TypeOf(bin_op->rhs)->UnwrapRef();
+        auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
+        auto* rhs_ty = src->TypeOf(bin_op->rhs)->UnwrapRef();
         BinaryOpSignature sig{bin_op->op, lhs_ty, rhs_ty};
         auto fn = binary_op_polyfills.GetOrCreate(sig, [&] {
             const bool is_div = bin_op->op == ast::BinaryOp::kDivide;
@@ -839,8 +993,8 @@
     /// @param bin_op the original BinaryExpression
     /// @return the polyfill divide or modulo
     const ast::Expression* PreciseFloatMod(const ast::BinaryExpression* bin_op) {
-        auto* lhs_ty = ctx.src->TypeOf(bin_op->lhs)->UnwrapRef();
-        auto* rhs_ty = ctx.src->TypeOf(bin_op->rhs)->UnwrapRef();
+        auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
+        auto* rhs_ty = src->TypeOf(bin_op->rhs)->UnwrapRef();
         BinaryOpSignature sig{bin_op->op, lhs_ty, rhs_ty};
         auto fn = binary_op_polyfills.GetOrCreate(sig, [&] {
             uint32_t lhs_width = 1;
@@ -887,24 +1041,8 @@
         return b.Call(fn, lhs, rhs);
     }
 
-  private:
-    /// The clone context
-    CloneContext& ctx;
-    /// The builtins to polyfill
-    Builtins polyfill;
-    /// The destination program builder
-    ProgramBuilder& b = *ctx.dst;
-    /// The source clone context
-    const sem::Info& sem = ctx.src->Sem();
-
-    // Polyfill functions for binary operators.
-    utils::Hashmap<BinaryOpSignature, Symbol, 8> binary_op_polyfills;
-
-    // Tracks whether the chromium_experimental_full_ptr_parameters extension has been enabled.
-    bool has_full_ptr_params;
-
     /// @returns the AST type for the given sem type
-    ast::Type T(const type::Type* ty) const { return CreateASTTypeFor(ctx, ty); }
+    ast::Type T(const type::Type* ty) { return CreateASTTypeFor(ctx, ty); }
 
     /// @returns 1 if `ty` is not a vector, otherwise the vector width
     uint32_t WidthOf(const type::Type* ty) const {
@@ -917,7 +1055,7 @@
     /// @returns a scalar or vector with the given width, with each element with
     /// the given value.
     template <typename T>
-    const ast::Expression* ScalarOrVector(uint32_t width, T value) const {
+    const ast::Expression* ScalarOrVector(uint32_t width, T value) {
         if (width == 1) {
             return b.Expr(value);
         }
@@ -931,6 +1069,204 @@
         }
         return b.Call(b.ty.vec<To>(width), e);
     }
+
+    /// Examines the call expression @p expr, applying any necessary polyfill transforms
+    void Call(const ast::CallExpression* expr) {
+        auto* call = src->Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
+        if (!call || call->Stage() == sem::EvaluationStage::kConstant ||
+            call->Stage() == sem::EvaluationStage::kNotEvaluated) {
+            return;  // Don't polyfill @const expressions
+        }
+        Symbol fn = Switch(
+            call->Target(),  //
+            [&](const sem::Builtin* builtin) {
+                switch (builtin->Type()) {
+                    case builtin::Function::kAcosh:
+                        if (cfg.builtins.acosh != Level::kNone) {
+                            return builtin_polyfills.GetOrCreate(
+                                builtin, [&] { return acosh(builtin->ReturnType()); });
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kAsinh:
+                        if (cfg.builtins.asinh) {
+                            return builtin_polyfills.GetOrCreate(
+                                builtin, [&] { return asinh(builtin->ReturnType()); });
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kAtanh:
+                        if (cfg.builtins.atanh != Level::kNone) {
+                            return builtin_polyfills.GetOrCreate(
+                                builtin, [&] { return atanh(builtin->ReturnType()); });
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kClamp:
+                        if (cfg.builtins.clamp_int) {
+                            auto& sig = builtin->Signature();
+                            if (sig.parameters[0]->Type()->is_integer_scalar_or_vector()) {
+                                return builtin_polyfills.GetOrCreate(
+                                    builtin, [&] { return clampInteger(builtin->ReturnType()); });
+                            }
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kCountLeadingZeros:
+                        if (cfg.builtins.count_leading_zeros) {
+                            return builtin_polyfills.GetOrCreate(
+                                builtin, [&] { return countLeadingZeros(builtin->ReturnType()); });
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kCountTrailingZeros:
+                        if (cfg.builtins.count_trailing_zeros) {
+                            return builtin_polyfills.GetOrCreate(
+                                builtin, [&] { return countTrailingZeros(builtin->ReturnType()); });
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kExtractBits:
+                        if (cfg.builtins.extract_bits != Level::kNone) {
+                            return builtin_polyfills.GetOrCreate(
+                                builtin, [&] { return extractBits(builtin->ReturnType()); });
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kFirstLeadingBit:
+                        if (cfg.builtins.first_leading_bit) {
+                            return builtin_polyfills.GetOrCreate(
+                                builtin, [&] { return firstLeadingBit(builtin->ReturnType()); });
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kFirstTrailingBit:
+                        if (cfg.builtins.first_trailing_bit) {
+                            return builtin_polyfills.GetOrCreate(
+                                builtin, [&] { return firstTrailingBit(builtin->ReturnType()); });
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kInsertBits:
+                        if (cfg.builtins.insert_bits != Level::kNone) {
+                            return builtin_polyfills.GetOrCreate(
+                                builtin, [&] { return insertBits(builtin->ReturnType()); });
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kReflect:
+                        // Only polyfill for vec2<f32>. See https://crbug.com/tint/1798 for
+                        // more details.
+                        if (cfg.builtins.reflect_vec2_f32) {
+                            auto& sig = builtin->Signature();
+                            auto* vec = sig.return_type->As<type::Vector>();
+                            if (vec && vec->Width() == 2 && vec->type()->Is<type::F32>()) {
+                                return builtin_polyfills.GetOrCreate(
+                                    builtin, [&] { return reflect(builtin->ReturnType()); });
+                            }
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kSaturate:
+                        if (cfg.builtins.saturate) {
+                            return builtin_polyfills.GetOrCreate(
+                                builtin, [&] { return saturate(builtin->ReturnType()); });
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kSign:
+                        if (cfg.builtins.sign_int) {
+                            auto* ty = builtin->ReturnType();
+                            if (ty->is_signed_integer_scalar_or_vector()) {
+                                return builtin_polyfills.GetOrCreate(builtin,
+                                                                     [&] { return sign_int(ty); });
+                            }
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kTextureSampleBaseClampToEdge:
+                        if (cfg.builtins.texture_sample_base_clamp_to_edge_2d_f32) {
+                            auto& sig = builtin->Signature();
+                            auto* tex = sig.Parameter(sem::ParameterUsage::kTexture);
+                            if (auto* stex = tex->Type()->As<type::SampledTexture>()) {
+                                if (stex->type()->Is<type::F32>()) {
+                                    return builtin_polyfills.GetOrCreate(builtin, [&] {
+                                        return textureSampleBaseClampToEdge_2d_f32();
+                                    });
+                                }
+                            }
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kTextureStore:
+                        if (cfg.builtins.bgra8unorm) {
+                            auto& sig = builtin->Signature();
+                            auto* tex = sig.Parameter(sem::ParameterUsage::kTexture);
+                            if (auto* stex = tex->Type()->As<type::StorageTexture>()) {
+                                if (stex->texel_format() == builtin::TexelFormat::kBgra8Unorm) {
+                                    size_t value_idx = static_cast<size_t>(
+                                        sig.IndexOf(sem::ParameterUsage::kValue));
+                                    ctx.Replace(expr, [this, expr, value_idx] {
+                                        utils::Vector<const ast::Expression*, 3> args;
+                                        for (auto* arg : expr->args) {
+                                            arg = ctx.Clone(arg);
+                                            if (args.Length() == value_idx) {  // value
+                                                arg = ctx.dst->MemberAccessor(arg, "bgra");
+                                            }
+                                            args.Push(arg);
+                                        }
+                                        return ctx.dst->Call(
+                                            utils::ToString(builtin::Function::kTextureStore),
+                                            std::move(args));
+                                    });
+                                    made_changes = true;
+                                }
+                            }
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kQuantizeToF16:
+                        if (cfg.builtins.quantize_to_vec_f16) {
+                            if (auto* vec = builtin->ReturnType()->As<type::Vector>()) {
+                                return builtin_polyfills.GetOrCreate(
+                                    builtin, [&] { return quantizeToF16(vec); });
+                            }
+                        }
+                        return Symbol{};
+
+                    case builtin::Function::kWorkgroupUniformLoad:
+                        if (cfg.builtins.workgroup_uniform_load) {
+                            return builtin_polyfills.GetOrCreate(builtin, [&] {
+                                return workgroupUniformLoad(builtin->ReturnType());
+                            });
+                        }
+                        return Symbol{};
+
+                    default:
+                        return Symbol{};
+                }
+            },
+            [&](const sem::ValueConversion* conv) {
+                if (cfg.builtins.conv_f32_to_iu32) {
+                    auto* src_ty = conv->Source();
+                    if (tint::Is<type::F32>(type::Type::ElementOf(src_ty))) {
+                        auto* dst_ty = conv->Target();
+                        if (tint::IsAnyOf<type::I32, type::U32>(type::Type::ElementOf(dst_ty))) {
+                            return f32_conv_polyfills.GetOrCreate(dst_ty, [&] {  //
+                                return ConvF32ToIU32(src_ty, dst_ty);
+                            });
+                        }
+                    }
+                }
+                return Symbol{};
+            });
+
+        if (fn.IsValid()) {
+            ctx.Replace(call->Declaration(),
+                        [this, fn, expr] { return ctx.dst->Call(fn, ctx.Clone(expr->args)); });
+            made_changes = true;
+        }
+    }
 };
 
 BuiltinPolyfill::BuiltinPolyfill() = default;
@@ -944,261 +1280,7 @@
     if (!cfg) {
         return SkipTransform;
     }
-
-    auto& polyfill = cfg->builtins;
-
-    utils::Hashmap<const sem::Builtin*, Symbol, 8> builtin_polyfills;
-
-    ProgramBuilder b;
-    CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
-    State s{ctx, polyfill};
-
-    bool made_changes = false;
-    for (auto* node : src->ASTNodes().Objects()) {
-        Switch(
-            node,
-            [&](const ast::CallExpression* expr) {
-                auto* call = src->Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
-                if (!call || call->Stage() == sem::EvaluationStage::kConstant ||
-                    call->Stage() == sem::EvaluationStage::kNotEvaluated) {
-                    return;  // Don't polyfill @const expressions
-                }
-                auto* builtin = call->Target()->As<sem::Builtin>();
-                if (!builtin) {
-                    return;
-                }
-                Symbol fn;
-                switch (builtin->Type()) {
-                    case sem::BuiltinType::kAcosh:
-                        if (polyfill.acosh != Level::kNone) {
-                            fn = builtin_polyfills.GetOrCreate(
-                                builtin, [&] { return s.acosh(builtin->ReturnType()); });
-                        }
-                        break;
-                    case sem::BuiltinType::kAsinh:
-                        if (polyfill.asinh) {
-                            fn = builtin_polyfills.GetOrCreate(
-                                builtin, [&] { return s.asinh(builtin->ReturnType()); });
-                        }
-                        break;
-                    case sem::BuiltinType::kAtanh:
-                        if (polyfill.atanh != Level::kNone) {
-                            fn = builtin_polyfills.GetOrCreate(
-                                builtin, [&] { return s.atanh(builtin->ReturnType()); });
-                        }
-                        break;
-                    case sem::BuiltinType::kClamp:
-                        if (polyfill.clamp_int) {
-                            auto& sig = builtin->Signature();
-                            if (sig.parameters[0]->Type()->is_integer_scalar_or_vector()) {
-                                fn = builtin_polyfills.GetOrCreate(
-                                    builtin, [&] { return s.clampInteger(builtin->ReturnType()); });
-                            }
-                        }
-                        break;
-                    case sem::BuiltinType::kCountLeadingZeros:
-                        if (polyfill.count_leading_zeros) {
-                            fn = builtin_polyfills.GetOrCreate(builtin, [&] {
-                                return s.countLeadingZeros(builtin->ReturnType());
-                            });
-                        }
-                        break;
-                    case sem::BuiltinType::kCountTrailingZeros:
-                        if (polyfill.count_trailing_zeros) {
-                            fn = builtin_polyfills.GetOrCreate(builtin, [&] {
-                                return s.countTrailingZeros(builtin->ReturnType());
-                            });
-                        }
-                        break;
-                    case sem::BuiltinType::kExtractBits:
-                        if (polyfill.extract_bits != Level::kNone) {
-                            fn = builtin_polyfills.GetOrCreate(
-                                builtin, [&] { return s.extractBits(builtin->ReturnType()); });
-                        }
-                        break;
-                    case sem::BuiltinType::kFirstLeadingBit:
-                        if (polyfill.first_leading_bit) {
-                            fn = builtin_polyfills.GetOrCreate(
-                                builtin, [&] { return s.firstLeadingBit(builtin->ReturnType()); });
-                        }
-                        break;
-                    case sem::BuiltinType::kFirstTrailingBit:
-                        if (polyfill.first_trailing_bit) {
-                            fn = builtin_polyfills.GetOrCreate(
-                                builtin, [&] { return s.firstTrailingBit(builtin->ReturnType()); });
-                        }
-                        break;
-                    case sem::BuiltinType::kInsertBits:
-                        if (polyfill.insert_bits != Level::kNone) {
-                            fn = builtin_polyfills.GetOrCreate(
-                                builtin, [&] { return s.insertBits(builtin->ReturnType()); });
-                        }
-                        break;
-                    case sem::BuiltinType::kReflect:
-                        // Only polyfill for vec2<f32>. See https://crbug.com/tint/1798 for more
-                        // details.
-                        if (polyfill.reflect_vec2_f32) {
-                            auto& sig = builtin->Signature();
-                            auto* vec = sig.return_type->As<type::Vector>();
-                            if (vec && vec->Width() == 2 && vec->type()->Is<type::F32>()) {
-                                fn = builtin_polyfills.GetOrCreate(
-                                    builtin, [&] { return s.reflect(builtin->ReturnType()); });
-                            }
-                        }
-                        break;
-                    case sem::BuiltinType::kSaturate:
-                        if (polyfill.saturate) {
-                            fn = builtin_polyfills.GetOrCreate(
-                                builtin, [&] { return s.saturate(builtin->ReturnType()); });
-                        }
-                        break;
-                    case sem::BuiltinType::kSign:
-                        if (polyfill.sign_int) {
-                            auto* ty = builtin->ReturnType();
-                            if (ty->is_signed_integer_scalar_or_vector()) {
-                                fn = builtin_polyfills.GetOrCreate(builtin,
-                                                                   [&] { return s.sign_int(ty); });
-                            }
-                        }
-                        break;
-                    case sem::BuiltinType::kTextureSampleBaseClampToEdge:
-                        if (polyfill.texture_sample_base_clamp_to_edge_2d_f32) {
-                            auto& sig = builtin->Signature();
-                            auto* tex = sig.Parameter(sem::ParameterUsage::kTexture);
-                            if (auto* stex = tex->Type()->As<type::SampledTexture>()) {
-                                if (stex->type()->Is<type::F32>()) {
-                                    fn = builtin_polyfills.GetOrCreate(builtin, [&] {
-                                        return s.textureSampleBaseClampToEdge_2d_f32();
-                                    });
-                                }
-                            }
-                        }
-                        break;
-                    case sem::BuiltinType::kTextureStore:
-                        if (polyfill.bgra8unorm) {
-                            auto& sig = builtin->Signature();
-                            auto* tex = sig.Parameter(sem::ParameterUsage::kTexture);
-                            if (auto* stex = tex->Type()->As<type::StorageTexture>()) {
-                                if (stex->texel_format() == builtin::TexelFormat::kBgra8Unorm) {
-                                    size_t value_idx = static_cast<size_t>(
-                                        sig.IndexOf(sem::ParameterUsage::kValue));
-                                    ctx.Replace(expr, [&ctx, expr, value_idx] {
-                                        utils::Vector<const ast::Expression*, 3> args;
-                                        for (auto* arg : expr->args) {
-                                            arg = ctx.Clone(arg);
-                                            if (args.Length() == value_idx) {  // value
-                                                arg = ctx.dst->MemberAccessor(arg, "bgra");
-                                            }
-                                            args.Push(arg);
-                                        }
-                                        return ctx.dst->Call(
-                                            utils::ToString(sem::BuiltinType::kTextureStore),
-                                            std::move(args));
-                                    });
-                                    made_changes = true;
-                                }
-                            }
-                        }
-                        break;
-                    case sem::BuiltinType::kQuantizeToF16:
-                        if (polyfill.quantize_to_vec_f16) {
-                            if (auto* vec = builtin->ReturnType()->As<type::Vector>()) {
-                                fn = builtin_polyfills.GetOrCreate(
-                                    builtin, [&] { return s.quantizeToF16(vec); });
-                            }
-                        }
-                        break;
-
-                    case sem::BuiltinType::kWorkgroupUniformLoad:
-                        if (polyfill.workgroup_uniform_load) {
-                            fn = builtin_polyfills.GetOrCreate(builtin, [&] {
-                                return s.workgroupUniformLoad(builtin->ReturnType());
-                            });
-                        }
-                        break;
-
-                    default:
-                        break;
-                }
-
-                if (fn.IsValid()) {
-                    ctx.Replace(call->Declaration(), [&ctx, fn, expr] {
-                        return ctx.dst->Call(fn, ctx.Clone(expr->args));
-                    });
-                    made_changes = true;
-                }
-            },
-            [&](const ast::BinaryExpression* bin_op) {
-                if (auto* sem = src->Sem().Get(bin_op);
-                    !sem || sem->Stage() == sem::EvaluationStage::kConstant ||
-                    sem->Stage() == sem::EvaluationStage::kNotEvaluated) {
-                    return;  // Don't polyfill @const expressions
-                }
-                switch (bin_op->op) {
-                    case ast::BinaryOp::kShiftLeft:
-                    case ast::BinaryOp::kShiftRight: {
-                        if (polyfill.bitshift_modulo) {
-                            ctx.Replace(bin_op, [bin_op, &s] { return s.BitshiftModulo(bin_op); });
-                            made_changes = true;
-                        }
-                        break;
-                    }
-                    case ast::BinaryOp::kDivide: {
-                        if (polyfill.int_div_mod) {
-                            auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
-                            if (lhs_ty->is_integer_scalar_or_vector()) {
-                                ctx.Replace(bin_op, [bin_op, &s] { return s.IntDivMod(bin_op); });
-                                made_changes = true;
-                            }
-                        }
-                        break;
-                    }
-                    case ast::BinaryOp::kModulo: {
-                        if (polyfill.int_div_mod) {
-                            auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
-                            if (lhs_ty->is_integer_scalar_or_vector()) {
-                                ctx.Replace(bin_op, [bin_op, &s] { return s.IntDivMod(bin_op); });
-                                made_changes = true;
-                            }
-                        }
-                        if (polyfill.precise_float_mod) {
-                            auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
-                            if (lhs_ty->is_float_scalar_or_vector()) {
-                                ctx.Replace(bin_op,
-                                            [bin_op, &s] { return s.PreciseFloatMod(bin_op); });
-                                made_changes = true;
-                            }
-                        }
-                        break;
-                    }
-                    default:
-                        break;
-                }
-            },
-            [&](const ast::Expression* expr) {
-                if (polyfill.bgra8unorm) {
-                    if (auto* ty_expr = src->Sem().Get<sem::TypeExpression>(expr)) {
-                        if (auto* tex = ty_expr->Type()->As<type::StorageTexture>()) {
-                            if (tex->texel_format() == builtin::TexelFormat::kBgra8Unorm) {
-                                ctx.Replace(expr, [&ctx, tex] {
-                                    return ctx.dst->Expr(ctx.dst->ty.storage_texture(
-                                        tex->dim(), builtin::TexelFormat::kRgba8Unorm,
-                                        tex->access()));
-                                });
-                                made_changes = true;
-                            }
-                        }
-                    }
-                }
-            });
-    }
-
-    if (!made_changes) {
-        return SkipTransform;
-    }
-
-    ctx.Clone();
-    return Program(std::move(b));
+    return State{src, *cfg}.Run();
 }
 
 BuiltinPolyfill::Config::Config(const Builtins& b) : builtins(b) {}
diff --git a/src/tint/transform/builtin_polyfill.h b/src/tint/transform/builtin_polyfill.h
index b070248..dee0bec 100644
--- a/src/tint/transform/builtin_polyfill.h
+++ b/src/tint/transform/builtin_polyfill.h
@@ -57,6 +57,8 @@
         bool count_leading_zeros = false;
         /// Should `countTrailingZeros()` be polyfilled?
         bool count_trailing_zeros = false;
+        /// Should converting f32 to i32 or u32 be polyfilled?
+        bool conv_f32_to_iu32 = false;
         /// What level should `extractBits()` be polyfilled?
         Level extract_bits = Level::kNone;
         /// Should `firstLeadingBit()` be polyfilled?
diff --git a/src/tint/transform/builtin_polyfill_test.cc b/src/tint/transform/builtin_polyfill_test.cc
index 8c0d787..65c1f29 100644
--- a/src/tint/transform/builtin_polyfill_test.cc
+++ b/src/tint/transform/builtin_polyfill_test.cc
@@ -815,6 +815,157 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// conv_f32_to_iu32
+////////////////////////////////////////////////////////////////////////////////
+DataMap polyfillConvF32ToIU32() {
+    BuiltinPolyfill::Builtins builtins;
+    builtins.conv_f32_to_iu32 = true;
+    DataMap data;
+    data.Add<BuiltinPolyfill::Config>(builtins);
+    return data;
+}
+
+TEST_F(BuiltinPolyfillTest, ShouldRunConvF32ToI32) {
+    auto* src = R"(
+fn f() {
+  let f = 42.0;
+  _ = i32(f);
+}
+)";
+
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
+    EXPECT_TRUE(ShouldRun<BuiltinPolyfill>(src, polyfillConvF32ToIU32()));
+}
+
+TEST_F(BuiltinPolyfillTest, ShouldRunConvF32ToU32) {
+    auto* src = R"(
+fn f() {
+  let f = 42.0;
+  _ = u32(f);
+}
+)";
+
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
+    EXPECT_TRUE(ShouldRun<BuiltinPolyfill>(src, polyfillConvF32ToIU32()));
+}
+
+TEST_F(BuiltinPolyfillTest, ShouldRunConvVec3F32ToVec3I32) {
+    auto* src = R"(
+fn f() {
+  let f = vec3(42.0);
+  _ = vec3<i32>(f);
+}
+)";
+
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
+    EXPECT_TRUE(ShouldRun<BuiltinPolyfill>(src, polyfillConvF32ToIU32()));
+}
+
+TEST_F(BuiltinPolyfillTest, ShouldRunConvVec3F32ToVec3U32) {
+    auto* src = R"(
+fn f() {
+  let f = vec3(42.0);
+  _ = vec3<u32>(f);
+}
+)";
+
+    EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
+    EXPECT_TRUE(ShouldRun<BuiltinPolyfill>(src, polyfillConvF32ToIU32()));
+}
+
+TEST_F(BuiltinPolyfillTest, ConvF32ToI32) {
+    auto* src = R"(
+fn f() {
+  let f = 42.0;
+  _ = i32(f);
+}
+)";
+    auto* expect = R"(
+fn tint_ftoi(v : f32) -> i32 {
+  return select(2147483647, select(i32(v), -2147483648, (v < -2147483648.0)), (v < 2147483520.0));
+}
+
+fn f() {
+  let f = 42.0;
+  _ = tint_ftoi(f);
+}
+)";
+
+    auto got = Run<BuiltinPolyfill>(src, polyfillConvF32ToIU32());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, ConvF32ToU32) {
+    auto* src = R"(
+fn f() {
+  let f = 42.0;
+  _ = u32(f);
+}
+)";
+    auto* expect = R"(
+fn tint_ftou(v : f32) -> u32 {
+  return select(4294967295, select(u32(v), 0, (v < 0.0)), (v < 4294967040.0));
+}
+
+fn f() {
+  let f = 42.0;
+  _ = tint_ftou(f);
+}
+)";
+
+    auto got = Run<BuiltinPolyfill>(src, polyfillConvF32ToIU32());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, ConvVec3F32ToVec3I32) {
+    auto* src = R"(
+fn f() {
+  let f = vec3(42.0);
+  _ = vec3<i32>(f);
+}
+)";
+    auto* expect = R"(
+fn tint_ftoi(v : vec3<f32>) -> vec3<i32> {
+  return select(vec3(2147483647), select(vec3<i32>(v), vec3(-2147483648), (v < vec3(-2147483648.0))), (v < vec3(2147483520.0)));
+}
+
+fn f() {
+  let f = vec3(42.0);
+  _ = tint_ftoi(f);
+}
+)";
+
+    auto got = Run<BuiltinPolyfill>(src, polyfillConvF32ToIU32());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, ConvVec3F32ToVec3U32) {
+    auto* src = R"(
+fn f() {
+  let f = vec3(42.0);
+  _ = vec3<u32>(f);
+}
+)";
+    auto* expect = R"(
+fn tint_ftou(v : vec3<f32>) -> vec3<u32> {
+  return select(vec3(4294967295), select(vec3<u32>(v), vec3(0), (v < vec3(0.0))), (v < vec3(4294967040.0)));
+}
+
+fn f() {
+  let f = vec3(42.0);
+  _ = tint_ftou(f);
+}
+)";
+
+    auto got = Run<BuiltinPolyfill>(src, polyfillConvF32ToIU32());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // countLeadingZeros
 ////////////////////////////////////////////////////////////////////////////////
 DataMap polyfillCountLeadingZeros() {
diff --git a/src/tint/transform/calculate_array_length.cc b/src/tint/transform/calculate_array_length.cc
index 9f5b659..743a992 100644
--- a/src/tint/transform/calculate_array_length.cc
+++ b/src/tint/transform/calculate_array_length.cc
@@ -26,6 +26,7 @@
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/struct.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/transform/simplify_pointers.h"
 #include "src/tint/type/reference.h"
 #include "src/tint/utils/hash.h"
@@ -44,7 +45,7 @@
     for (auto* fn : program->AST().Functions()) {
         if (auto* sem_fn = program->Sem().Get(fn)) {
             for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
-                if (builtin->Type() == sem::BuiltinType::kArrayLength) {
+                if (builtin->Type() == builtin::Function::kArrayLength) {
                     return true;
                 }
             }
@@ -130,7 +131,7 @@
         if (auto* call_expr = node->As<ast::CallExpression>()) {
             auto* call = sem.Get(call_expr)->UnwrapMaterialize()->As<sem::Call>();
             if (auto* builtin = call->Target()->As<sem::Builtin>()) {
-                if (builtin->Type() == sem::BuiltinType::kArrayLength) {
+                if (builtin->Type() == builtin::Function::kArrayLength) {
                     // We're dealing with an arrayLength() call
 
                     if (auto* call_stmt = call->Stmt()->Declaration()->As<ast::CallStatement>()) {
diff --git a/src/tint/transform/combine_samplers.cc b/src/tint/transform/combine_samplers.cc
index 7b9f9fd..ca8111c 100644
--- a/src/tint/transform/combine_samplers.cc
+++ b/src/tint/transform/combine_samplers.cc
@@ -276,7 +276,7 @@
                         }
                     }
                     const ast::Expression* value = ctx.dst->Call(ctx.Clone(expr->target), args);
-                    if (builtin->Type() == sem::BuiltinType::kTextureLoad &&
+                    if (builtin->Type() == builtin::Function::kTextureLoad &&
                         texture_var->Type()->UnwrapRef()->Is<type::DepthTexture>() &&
                         !call->Stmt()->Declaration()->Is<ast::CallStatement>()) {
                         value = ctx.dst->MemberAccessor(value, "x");
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index f3d92c5..f2146f3 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -30,6 +30,7 @@
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/struct.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/array.h"
 #include "src/tint/type/atomic.h"
 #include "src/tint/type/reference.h"
@@ -124,7 +125,7 @@
 /// AtomicKey is the unordered map key to an atomic intrinsic.
 struct AtomicKey {
     type::Type const* el_ty = nullptr;  // element type
-    sem::BuiltinType const op;          // atomic op
+    builtin::Function const op;         // atomic op
     Symbol const buffer;                // buffer name
     bool operator==(const AtomicKey& rhs) const {
         return el_ty == rhs.el_ty && op == rhs.op && buffer == rhs.buffer;
@@ -248,42 +249,42 @@
 /// @returns a DecomposeMemoryAccess::Intrinsic attribute that can be applied to a stub function for
 /// the atomic op and the type @p ty.
 DecomposeMemoryAccess::Intrinsic* IntrinsicAtomicFor(ProgramBuilder* builder,
-                                                     sem::BuiltinType ity,
+                                                     builtin::Function ity,
                                                      const type::Type* ty,
                                                      const Symbol& buffer) {
     auto op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicLoad;
     switch (ity) {
-        case sem::BuiltinType::kAtomicLoad:
+        case builtin::Function::kAtomicLoad:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicLoad;
             break;
-        case sem::BuiltinType::kAtomicStore:
+        case builtin::Function::kAtomicStore:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicStore;
             break;
-        case sem::BuiltinType::kAtomicAdd:
+        case builtin::Function::kAtomicAdd:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicAdd;
             break;
-        case sem::BuiltinType::kAtomicSub:
+        case builtin::Function::kAtomicSub:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicSub;
             break;
-        case sem::BuiltinType::kAtomicMax:
+        case builtin::Function::kAtomicMax:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicMax;
             break;
-        case sem::BuiltinType::kAtomicMin:
+        case builtin::Function::kAtomicMin:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicMin;
             break;
-        case sem::BuiltinType::kAtomicAnd:
+        case builtin::Function::kAtomicAnd:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicAnd;
             break;
-        case sem::BuiltinType::kAtomicOr:
+        case builtin::Function::kAtomicOr:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicOr;
             break;
-        case sem::BuiltinType::kAtomicXor:
+        case builtin::Function::kAtomicXor:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicXor;
             break;
-        case sem::BuiltinType::kAtomicExchange:
+        case builtin::Function::kAtomicExchange:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicExchange;
             break;
-        case sem::BuiltinType::kAtomicCompareExchangeWeak:
+        case builtin::Function::kAtomicCompareExchangeWeak:
             op = DecomposeMemoryAccess::Intrinsic::Op::kAtomicCompareExchangeWeak;
             break;
         default:
@@ -658,7 +659,7 @@
             ast::Type ret_ty;
 
             // For intrinsics that return a struct, there is no AST node for it, so create one now.
-            if (intrinsic->Type() == sem::BuiltinType::kAtomicCompareExchangeWeak) {
+            if (intrinsic->Type() == builtin::Function::kAtomicCompareExchangeWeak) {
                 auto* str = intrinsic->ReturnType()->As<sem::Struct>();
                 TINT_ASSERT(Transform, str && str->Declaration() == nullptr);
 
@@ -937,7 +938,7 @@
         if (auto* call_expr = node->As<ast::CallExpression>()) {
             auto* call = sem.Get(call_expr)->UnwrapMaterialize()->As<sem::Call>();
             if (auto* builtin = call->Target()->As<sem::Builtin>()) {
-                if (builtin->Type() == sem::BuiltinType::kArrayLength) {
+                if (builtin->Type() == builtin::Function::kArrayLength) {
                     // arrayLength(X)
                     // Don't convert X into a load, this builtin actually requires the real pointer.
                     state.TakeAccess(call_expr->args[0]);
diff --git a/src/tint/transform/demote_to_helper.cc b/src/tint/transform/demote_to_helper.cc
index ac3c15e..c853ef0 100644
--- a/src/tint/transform/demote_to_helper.cc
+++ b/src/tint/transform/demote_to_helper.cc
@@ -23,6 +23,7 @@
 #include "src/tint/sem/call.h"
 #include "src/tint/sem/function.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/switch.h"
 #include "src/tint/transform/utils/hoist_to_decl_before.h"
 #include "src/tint/type/reference.h"
 #include "src/tint/utils/map.h"
@@ -152,13 +153,13 @@
                     return;
                 }
 
-                if (builtin->Type() == sem::BuiltinType::kTextureStore) {
+                if (builtin->Type() == builtin::Function::kTextureStore) {
                     // A call to textureStore() will always be a statement.
                     // Wrap it inside a conditional block.
                     auto* masked_call = b.If(b.Not(flag), b.Block(ctx.Clone(stmt->Declaration())));
                     ctx.Replace(stmt->Declaration(), masked_call);
                 } else if (builtin->IsAtomic() &&
-                           builtin->Type() != sem::BuiltinType::kAtomicLoad) {
+                           builtin->Type() != builtin::Function::kAtomicLoad) {
                     // A call to an atomic builtin can be a statement or an expression.
                     if (auto* call_stmt = stmt->Declaration()->As<ast::CallStatement>();
                         call_stmt && call_stmt->expr == call) {
@@ -179,7 +180,7 @@
                         auto result = b.Sym();
                         ast::Type result_ty;
                         const ast::Statement* masked_call = nullptr;
-                        if (builtin->Type() == sem::BuiltinType::kAtomicCompareExchangeWeak) {
+                        if (builtin->Type() == builtin::Function::kAtomicCompareExchangeWeak) {
                             // Special case for atomicCompareExchangeWeak as we cannot name its
                             // result type. We have to declare an equivalent struct and copy the
                             // original member values over to it.
diff --git a/src/tint/transform/merge_return.cc b/src/tint/transform/merge_return.cc
index 353fb13..f9f2feb 100644
--- a/src/tint/transform/merge_return.cc
+++ b/src/tint/transform/merge_return.cc
@@ -18,6 +18,7 @@
 
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/switch.h"
 #include "src/tint/utils/scoped_assignment.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::MergeReturn);
diff --git a/src/tint/transform/multiplanar_external_texture.cc b/src/tint/transform/multiplanar_external_texture.cc
index 27eeeb0..310bc46 100644
--- a/src/tint/transform/multiplanar_external_texture.cc
+++ b/src/tint/transform/multiplanar_external_texture.cc
@@ -121,7 +121,7 @@
             // The binding points for the newly introduced bindings must have been provided to this
             // transform. We fetch the new binding points by providing the original texture_external
             // binding points into the passed map.
-            BindingPoint bp = sem_var->BindingPoint();
+            sem::BindingPoint bp = sem_var->BindingPoint();
 
             BindingsMap::const_iterator it = new_binding_points->bindings_map.find(bp);
             if (it == new_binding_points->bindings_map.end()) {
@@ -196,7 +196,7 @@
 
             if (builtin && !builtin->Parameters().IsEmpty() &&
                 builtin->Parameters()[0]->Type()->Is<type::ExternalTexture>() &&
-                builtin->Type() != sem::BuiltinType::kTextureDimensions) {
+                builtin->Type() != builtin::Function::kTextureDimensions) {
                 if (auto* var_user =
                         sem.GetVal(expr->args[0])->UnwrapLoad()->As<sem::VariableUser>()) {
                     auto it = new_binding_symbols.find(var_user->Variable());
@@ -210,9 +210,9 @@
                     auto& syms = it->second;
 
                     switch (builtin->Type()) {
-                        case sem::BuiltinType::kTextureLoad:
+                        case builtin::Function::kTextureLoad:
                             return createTextureLoad(call, syms);
-                        case sem::BuiltinType::kTextureSampleBaseClampToEdge:
+                        case builtin::Function::kTextureSampleBaseClampToEdge:
                             return createTextureSampleBaseClampToEdge(expr, syms);
                         default:
                             break;
@@ -310,13 +310,13 @@
     /// builtin function.
     /// @param call_type determines which function body to generate
     /// @returns a statement list that makes of the body of the chosen function
-    auto buildTextureBuiltinBody(sem::BuiltinType call_type) {
+    auto buildTextureBuiltinBody(builtin::Function call_type) {
         utils::Vector<const ast::Statement*, 16> stmts;
         const ast::CallExpression* single_plane_call = nullptr;
         const ast::CallExpression* plane_0_call = nullptr;
         const ast::CallExpression* plane_1_call = nullptr;
         switch (call_type) {
-            case sem::BuiltinType::kTextureSampleBaseClampToEdge:
+            case builtin::Function::kTextureSampleBaseClampToEdge:
                 stmts.Push(b.Decl(b.Let(
                     "modifiedCoords", b.Mul(b.MemberAccessor("params", "coordTransformationMatrix"),
                                             b.vec3<f32>("coord", 1_a)))));
@@ -346,7 +346,7 @@
                 // textureSampleLevel(plane1, smp, plane1_clamped, 0.0);
                 plane_1_call = b.Call("textureSampleLevel", "plane1", "smp", "plane1_clamped", 0_a);
                 break;
-            case sem::BuiltinType::kTextureLoad:
+            case builtin::Function::kTextureLoad:
                 // textureLoad(plane0, coord, 0);
                 single_plane_call = b.Call("textureLoad", "plane0", "coord", 0_a);
                 // textureLoad(plane0, coord, 0);
@@ -433,7 +433,7 @@
                        b.Param("params", b.ty(params_struct_sym)),
                    },
                    b.ty.vec4(b.ty.f32()),
-                   buildTextureBuiltinBody(sem::BuiltinType::kTextureSampleBaseClampToEdge));
+                   buildTextureBuiltinBody(builtin::Function::kTextureSampleBaseClampToEdge));
         }
 
         return b.Call(texture_sample_external_sym, utils::Vector{
@@ -480,7 +480,7 @@
                        b.Param("params", b.ty(params_struct_sym)),
                    },
                    b.ty.vec4(b.ty.f32()),  //
-                   buildTextureBuiltinBody(sem::BuiltinType::kTextureLoad));
+                   buildTextureBuiltinBody(builtin::Function::kTextureLoad));
 
             return name;
         });
@@ -494,6 +494,7 @@
 
 MultiplanarExternalTexture::NewBindingPoints::NewBindingPoints(BindingsMap inputBindingsMap)
     : bindings_map(std::move(inputBindingsMap)) {}
+
 MultiplanarExternalTexture::NewBindingPoints::~NewBindingPoints() = default;
 
 MultiplanarExternalTexture::MultiplanarExternalTexture() = default;
diff --git a/src/tint/transform/multiplanar_external_texture.h b/src/tint/transform/multiplanar_external_texture.h
index 695e38c..656d0ad 100644
--- a/src/tint/transform/multiplanar_external_texture.h
+++ b/src/tint/transform/multiplanar_external_texture.h
@@ -19,15 +19,13 @@
 #include <utility>
 
 #include "src/tint/ast/struct_member.h"
+#include "src/tint/builtin/function.h"
 #include "src/tint/sem/binding_point.h"
-#include "src/tint/sem/builtin_type.h"
+#include "src/tint/sem/external_texture.h"
 #include "src/tint/transform/transform.h"
 
 namespace tint::transform {
 
-/// BindingPoint is an alias to sem::BindingPoint
-using BindingPoint = sem::BindingPoint;
-
 /// Within the MultiplanarExternalTexture transform, each instance of a
 /// texture_external binding is unpacked into two texture_2d<f32> bindings
 /// representing two possible planes of a texture and a uniform buffer binding
@@ -43,22 +41,12 @@
   public:
     /// This struct identifies the binding groups and locations for new bindings to
     /// use when transforming a texture_external instance.
-    struct BindingPoints {
-        /// The desired binding location of the texture_2d representing plane #1 when
-        /// a texture_external binding is expanded.
-        BindingPoint plane_1;
-        /// The desired binding location of the ExternalTextureParams uniform when a
-        /// texture_external binding is expanded.
-        BindingPoint params;
-
-        /// Reflect the fields of this class so that it can be used by tint::ForeachField()
-        TINT_REFLECT(plane_1, params);
-    };
+    using BindingPoints = sem::external_texture::BindingPoints;
 
     /// BindingsMap is a map where the key is the binding location of a
     /// texture_external and the value is a struct containing the desired
     /// locations for new bindings expanded from the texture_external instance.
-    using BindingsMap = std::unordered_map<BindingPoint, BindingPoints>;
+    using BindingsMap = sem::external_texture::BindingsMap;
 
     /// NewBindingPoints is consumed by the MultiplanarExternalTexture transform.
     /// Data holds information about location of each texture_external binding and
diff --git a/src/tint/transform/packed_vec3.cc b/src/tint/transform/packed_vec3.cc
index 956adcb..8a23221 100644
--- a/src/tint/transform/packed_vec3.cc
+++ b/src/tint/transform/packed_vec3.cc
@@ -27,6 +27,7 @@
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/type_expression.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/array.h"
 #include "src/tint/type/reference.h"
 #include "src/tint/type/vector.h"
diff --git a/src/tint/transform/preserve_padding.cc b/src/tint/transform/preserve_padding.cc
index fbe785c..e8c95e3 100644
--- a/src/tint/transform/preserve_padding.cc
+++ b/src/tint/transform/preserve_padding.cc
@@ -19,6 +19,7 @@
 
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/struct.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/reference.h"
 #include "src/tint/utils/map.h"
 #include "src/tint/utils/vector.h"
@@ -66,8 +67,8 @@
                 },
                 [&](const ast::Enable* enable) {
                     // Check if the full pointer parameters extension is already enabled.
-                    if (enable->extension ==
-                        builtin::Extension::kChromiumExperimentalFullPtrParameters) {
+                    if (enable->HasExtension(
+                            builtin::Extension::kChromiumExperimentalFullPtrParameters)) {
                         ext_enabled = true;
                     }
                 });
diff --git a/src/tint/transform/promote_side_effects_to_decl.cc b/src/tint/transform/promote_side_effects_to_decl.cc
index 65ec731..9db7e38 100644
--- a/src/tint/transform/promote_side_effects_to_decl.cc
+++ b/src/tint/transform/promote_side_effects_to_decl.cc
@@ -337,9 +337,8 @@
             });
     }
 
-    // Starts the recursive processing of a statement's expression(s) to hoist
-    // side-effects to lets.
-    void ProcessStatement(const ast::Expression* expr) {
+    // Starts the recursive processing of a statement's expression(s) to hoist side-effects to lets.
+    void ProcessExpression(const ast::Expression* expr) {
         if (!expr) {
             return;
         }
@@ -348,31 +347,6 @@
         ProcessExpression(expr, maybe_hoist);
     }
 
-    // Special case for processing assignment statement expressions, as we must
-    // evaluate the rhs before the lhs, and possibly hoist the rhs expression.
-    void ProcessAssignment(const ast::Expression* lhs, const ast::Expression* rhs) {
-        // Evaluate rhs before lhs
-        tint::utils::Vector<const ast::Expression*, 8> maybe_hoist;
-        if (ProcessExpression(rhs, maybe_hoist)) {
-            maybe_hoist.Push(rhs);
-        }
-
-        // If the rhs has side-effects, it may affect the lhs, so hoist it right
-        // away. e.g. "b[c] = a(0);"
-        if (HasSideEffects(rhs)) {
-            // Technically, we can always hoist rhs, but don't bother doing so when
-            // the lhs is just a variable or phony.
-            if (!lhs->IsAnyOf<ast::IdentifierExpression, ast::PhonyExpression>()) {
-                Flush(maybe_hoist);
-            }
-        }
-
-        // If maybe_hoist still has values, it means they are potential side-effect
-        // receivers. We pass this in while processing the lhs, in which case they
-        // may get hoisted if the lhs has side-effects. E.g. "b[a(0)] = c;".
-        ProcessExpression(lhs, maybe_hoist);
-    }
-
   public:
     explicit CollectHoistsState(CloneContext& ctx_in) : StateBase(ctx_in) {}
 
@@ -386,21 +360,26 @@
             }
 
             Switch(
-                stmt, [&](const ast::AssignmentStatement* s) { ProcessAssignment(s->lhs, s->rhs); },
-                [&](const ast::CallStatement* s) {  //
-                    ProcessStatement(s->expr);
+                stmt,  //
+                [&](const ast::AssignmentStatement* s) {
+                    tint::utils::Vector<const ast::Expression*, 8> maybe_hoist;
+                    ProcessExpression(s->lhs, maybe_hoist);
+                    ProcessExpression(s->rhs, maybe_hoist);
                 },
-                [&](const ast::ForLoopStatement* s) { ProcessStatement(s->condition); },
-                [&](const ast::WhileStatement* s) { ProcessStatement(s->condition); },
+                [&](const ast::CallStatement* s) {  //
+                    ProcessExpression(s->expr);
+                },
+                [&](const ast::ForLoopStatement* s) { ProcessExpression(s->condition); },
+                [&](const ast::WhileStatement* s) { ProcessExpression(s->condition); },
                 [&](const ast::IfStatement* s) {  //
-                    ProcessStatement(s->condition);
+                    ProcessExpression(s->condition);
                 },
                 [&](const ast::ReturnStatement* s) {  //
-                    ProcessStatement(s->value);
+                    ProcessExpression(s->value);
                 },
-                [&](const ast::SwitchStatement* s) { ProcessStatement(s->condition); },
+                [&](const ast::SwitchStatement* s) { ProcessExpression(s->condition); },
                 [&](const ast::VariableDeclStatement* s) {
-                    ProcessStatement(s->variable->initializer);
+                    ProcessExpression(s->variable->initializer);
                 });
         }
 
@@ -563,10 +542,10 @@
                     !sem.GetVal(s->rhs)->HasSideEffects()) {
                     return nullptr;
                 }
-                // rhs before lhs
+                // lhs before rhs
                 tint::utils::Vector<const ast::Statement*, 8> stmts;
-                ctx.Replace(s->rhs, Decompose(s->rhs, &stmts));
                 ctx.Replace(s->lhs, Decompose(s->lhs, &stmts));
+                ctx.Replace(s->rhs, Decompose(s->rhs, &stmts));
                 InsertBefore(stmts, s);
                 return ctx.CloneWithoutTransform(s);
             },
diff --git a/src/tint/transform/promote_side_effects_to_decl_test.cc b/src/tint/transform/promote_side_effects_to_decl_test.cc
index 6ed4b27..636a809 100644
--- a/src/tint/transform/promote_side_effects_to_decl_test.cc
+++ b/src/tint/transform/promote_side_effects_to_decl_test.cc
@@ -2830,9 +2830,8 @@
 
 fn f() {
   var b = array<i32, 10>();
-  let tint_symbol = a(1);
-  let tint_symbol_1 = a(0);
-  b[tint_symbol_1] = tint_symbol;
+  let tint_symbol = a(0);
+  b[tint_symbol] = a(1);
 }
 )";
 
@@ -2861,10 +2860,9 @@
 
 fn f() {
   var b = array<array<i32, 10>, 10>();
-  let tint_symbol = a(2);
-  let tint_symbol_1 = a(0);
-  let tint_symbol_2 = a(1);
-  b[tint_symbol_1][tint_symbol_2] = tint_symbol;
+  let tint_symbol = a(0);
+  let tint_symbol_1 = a(1);
+  b[tint_symbol][tint_symbol_1] = a(2);
 }
 )";
 
@@ -2893,11 +2891,10 @@
 
 fn f() {
   var b = array<array<array<i32, 10>, 10>, 10>();
-  let tint_symbol = a(3);
-  let tint_symbol_1 = a(0);
-  let tint_symbol_2 = a(1);
-  let tint_symbol_3 = a(2);
-  b[tint_symbol_1][tint_symbol_2][tint_symbol_3] = tint_symbol;
+  let tint_symbol = a(0);
+  let tint_symbol_1 = a(1);
+  let tint_symbol_2 = a(2);
+  b[tint_symbol][tint_symbol_1][tint_symbol_2] = a(3);
 }
 )";
 
@@ -2930,11 +2927,9 @@
   var b = array<i32, 3>();
   var d = array<array<i32, 3>, 3>();
   var a_1 = 0;
-  let tint_symbol = a(0);
-  let tint_symbol_1 = a_1;
-  let tint_symbol_2 = d[tint_symbol][tint_symbol_1];
-  let tint_symbol_3 = a(2);
-  b[tint_symbol_3] = tint_symbol_2;
+  let tint_symbol = a(2);
+  let tint_symbol_1 = a(0);
+  b[tint_symbol] = d[tint_symbol_1][a_1];
 }
 )";
 
@@ -2963,9 +2958,8 @@
 
 fn f() {
   var b = vec3<i32>();
-  let tint_symbol = a(1);
-  let tint_symbol_1 = a(0);
-  b[tint_symbol_1] = tint_symbol;
+  let tint_symbol = a(0);
+  b[tint_symbol] = a(1);
 }
 )";
 
@@ -2996,9 +2990,8 @@
 fn f() {
   var b = vec3<i32>();
   var c = 0;
-  let tint_symbol = c;
-  let tint_symbol_1 = a(0);
-  b[tint_symbol_1] = tint_symbol;
+  let tint_symbol = a(0);
+  b[tint_symbol] = c;
 }
 )";
 
@@ -3029,8 +3022,8 @@
 fn f() {
   var b = vec3<i32>();
   var c = 0;
-  let tint_symbol = a(0);
-  b[c] = tint_symbol;
+  let tint_symbol = c;
+  b[tint_symbol] = a(0);
 }
 )";
 
diff --git a/src/tint/transform/renamer.cc b/src/tint/transform/renamer.cc
index 3ddb6c6..708c6ec 100644
--- a/src/tint/transform/renamer.cc
+++ b/src/tint/transform/renamer.cc
@@ -24,6 +24,7 @@
 #include "src/tint/sem/type_expression.h"
 #include "src/tint/sem/value_constructor.h"
 #include "src/tint/sem/value_conversion.h"
+#include "src/tint/switch.h"
 #include "src/tint/text/unicode.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::Renamer);
diff --git a/src/tint/transform/robustness.cc b/src/tint/transform/robustness.cc
index b79a029..2eb74a1 100644
--- a/src/tint/transform/robustness.cc
+++ b/src/tint/transform/robustness.cc
@@ -28,6 +28,7 @@
 #include "src/tint/sem/member_accessor_expression.h"
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/value_expression.h"
+#include "src/tint/switch.h"
 #include "src/tint/transform/utils/hoist_to_decl_before.h"
 #include "src/tint/type/reference.h"
 
@@ -231,7 +232,7 @@
                     // Must clamp, even if the index is constant.
 
                     auto* arr_ptr = b.AddressOf(ctx.Clone(expr->Object()->Declaration()));
-                    return b.Sub(b.Call(sem::BuiltinType::kArrayLength, arr_ptr), 1_u);
+                    return b.Sub(b.Call(builtin::Function::kArrayLength, arr_ptr), 1_u);
                 }
                 if (auto count = arr->ConstantCount()) {
                     if (expr->Index()->ConstantValue()) {
@@ -343,7 +344,7 @@
         if (expr_sem->Index()->Type()->is_signed_integer_scalar()) {
             idx = b.Call<u32>(idx);  // u32(idx)
         }
-        auto* clamped_idx = b.Call(sem::BuiltinType::kMin, idx, max);
+        auto* clamped_idx = b.Call(builtin::Function::kMin, idx, max);
         ctx.Replace(expr->Declaration()->index, clamped_idx);
     }
 
@@ -358,14 +359,14 @@
         }
 
         if (predicate) {
-            if (builtin->Type() == sem::BuiltinType::kWorkgroupUniformLoad) {
+            if (builtin->Type() == builtin::Function::kWorkgroupUniformLoad) {
                 // https://www.w3.org/TR/WGSL/#workgroupUniformLoad-builtin:
                 //  "Executes a control barrier synchronization function that affects memory and
                 //   atomic operations in the workgroup address space."
                 // Because the call acts like a control barrier, we need to make sure that we still
                 // trigger a workgroup barrier if the predicate fails.
                 PredicateCall(call, predicate,
-                              b.Block(b.CallStmt(b.Call(sem::BuiltinType::kWorkgroupBarrier))));
+                              b.Block(b.CallStmt(b.Call(builtin::Function::kWorkgroupBarrier))));
             } else {
                 PredicateCall(call, predicate);
             }
@@ -408,7 +409,7 @@
                 // let num_levels = textureNumLevels(texture-arg);
                 num_levels = b.Symbols().New("num_levels");
                 hoist.InsertBefore(
-                    stmt, b.Decl(b.Let(num_levels, b.Call(sem::BuiltinType::kTextureNumLevels,
+                    stmt, b.Decl(b.Let(num_levels, b.Call(builtin::Function::kTextureNumLevels,
                                                           ctx.Clone(texture_arg)))));
 
                 // predicate: level_idx < num_levels
@@ -433,12 +434,12 @@
                 // predicate: all(coords < textureDimensions(texture))
                 auto* dimensions =
                     level_idx.IsValid()
-                        ? b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg),
-                                 b.Call(sem::BuiltinType::kMin, b.Expr(level_idx),
+                        ? b.Call(builtin::Function::kTextureDimensions, ctx.Clone(texture_arg),
+                                 b.Call(builtin::Function::kMin, b.Expr(level_idx),
                                         b.Sub(num_levels, 1_a)))
-                        : b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg));
+                        : b.Call(builtin::Function::kTextureDimensions, ctx.Clone(texture_arg));
                 predicate =
-                    And(predicate, b.Call(sem::BuiltinType::kAll, b.LessThan(coords, dimensions)));
+                    And(predicate, b.Call(builtin::Function::kAll, b.LessThan(coords, dimensions)));
 
                 // Replace the level argument with `coord`
                 ctx.Replace(arg, b.Expr(coords));
@@ -448,7 +449,7 @@
         if (array_arg_idx >= 0) {
             // let array_idx = u32(array-arg)
             auto* arg = expr->args[static_cast<size_t>(array_arg_idx)];
-            auto* num_layers = b.Call(sem::BuiltinType::kTextureNumLayers, ctx.Clone(texture_arg));
+            auto* num_layers = b.Call(builtin::Function::kTextureNumLayers, ctx.Clone(texture_arg));
             auto array_idx = b.Symbols().New("array_idx");
             hoist.InsertBefore(stmt, b.Decl(b.Let(array_idx, CastToUnsigned(ctx.Clone(arg), 1u))));
 
@@ -493,10 +494,10 @@
                 const auto* arg = expr->args[static_cast<size_t>(level_arg_idx)];
                 level_idx = b.Symbols().New("level_idx");
                 const auto* num_levels =
-                    b.Call(sem::BuiltinType::kTextureNumLevels, ctx.Clone(texture_arg));
+                    b.Call(builtin::Function::kTextureNumLevels, ctx.Clone(texture_arg));
                 const auto* max = b.Sub(num_levels, 1_a);
                 hoist.InsertBefore(
-                    stmt, b.Decl(b.Let(level_idx, b.Call(sem::BuiltinType::kMin,
+                    stmt, b.Decl(b.Let(level_idx, b.Call(builtin::Function::kMin,
                                                          b.Call<u32>(ctx.Clone(arg)), max))));
                 ctx.Replace(arg, b.Expr(level_idx));
             }
@@ -510,19 +511,19 @@
                 const auto width = WidthOf(param->Type());
                 const auto* dimensions =
                     level_idx.IsValid()
-                        ? b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg),
+                        ? b.Call(builtin::Function::kTextureDimensions, ctx.Clone(texture_arg),
                                  level_idx)
-                        : b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg));
+                        : b.Call(builtin::Function::kTextureDimensions, ctx.Clone(texture_arg));
 
                 // dimensions is u32 or vecN<u32>
                 const auto* unsigned_max = b.Sub(dimensions, ScalarOrVec(b.Expr(1_a), width));
                 if (param->Type()->is_signed_integer_scalar_or_vector()) {
                     const auto* zero = ScalarOrVec(b.Expr(0_a), width);
                     const auto* signed_max = CastToSigned(unsigned_max, width);
-                    ctx.Replace(arg,
-                                b.Call(sem::BuiltinType::kClamp, ctx.Clone(arg), zero, signed_max));
+                    ctx.Replace(
+                        arg, b.Call(builtin::Function::kClamp, ctx.Clone(arg), zero, signed_max));
                 } else {
-                    ctx.Replace(arg, b.Call(sem::BuiltinType::kMin, ctx.Clone(arg), unsigned_max));
+                    ctx.Replace(arg, b.Call(builtin::Function::kMin, ctx.Clone(arg), unsigned_max));
                 }
             }
         }
@@ -531,14 +532,15 @@
         if (array_arg_idx >= 0) {
             auto* param = builtin->Parameters()[static_cast<size_t>(array_arg_idx)];
             auto* arg = expr->args[static_cast<size_t>(array_arg_idx)];
-            auto* num_layers = b.Call(sem::BuiltinType::kTextureNumLayers, ctx.Clone(texture_arg));
+            auto* num_layers = b.Call(builtin::Function::kTextureNumLayers, ctx.Clone(texture_arg));
 
             const auto* unsigned_max = b.Sub(num_layers, 1_a);
             if (param->Type()->is_signed_integer_scalar()) {
                 const auto* signed_max = CastToSigned(unsigned_max, 1u);
-                ctx.Replace(arg, b.Call(sem::BuiltinType::kClamp, ctx.Clone(arg), 0_a, signed_max));
+                ctx.Replace(arg,
+                            b.Call(builtin::Function::kClamp, ctx.Clone(arg), 0_a, signed_max));
             } else {
-                ctx.Replace(arg, b.Call(sem::BuiltinType::kMin, ctx.Clone(arg), unsigned_max));
+                ctx.Replace(arg, b.Call(builtin::Function::kMin, ctx.Clone(arg), unsigned_max));
             }
         }
     }
@@ -546,9 +548,10 @@
     /// @param type builtin type
     /// @returns true if the given builtin is a texture function that requires predication or
     /// clamping of arguments.
-    bool TextureBuiltinNeedsRobustness(sem::BuiltinType type) {
-        return type == sem::BuiltinType::kTextureLoad || type == sem::BuiltinType::kTextureStore ||
-               type == sem::BuiltinType::kTextureDimensions;
+    bool TextureBuiltinNeedsRobustness(builtin::Function type) {
+        return type == builtin::Function::kTextureLoad ||
+               type == builtin::Function::kTextureStore ||
+               type == builtin::Function::kTextureDimensions;
     }
 
     /// @returns a bitwise and of the two expressions, or the other expression if one is null.
diff --git a/src/tint/transform/simplify_pointers.cc b/src/tint/transform/simplify_pointers.cc
index e9c719e..bec9019 100644
--- a/src/tint/transform/simplify_pointers.cc
+++ b/src/tint/transform/simplify_pointers.cc
@@ -24,6 +24,7 @@
 #include "src/tint/sem/function.h"
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/transform/unshadow.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::SimplifyPointers);
diff --git a/src/tint/transform/single_entry_point.cc b/src/tint/transform/single_entry_point.cc
index 8a8daeb..8be90bb 100644
--- a/src/tint/transform/single_entry_point.cc
+++ b/src/tint/transform/single_entry_point.cc
@@ -20,6 +20,7 @@
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/function.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::SingleEntryPoint);
 TINT_INSTANTIATE_TYPEINFO(tint::transform::SingleEntryPoint::Config);
diff --git a/src/tint/transform/spirv_atomic.cc b/src/tint/transform/spirv_atomic.cc
index 043861b..f41fbb7 100644
--- a/src/tint/transform/spirv_atomic.cc
+++ b/src/tint/transform/spirv_atomic.cc
@@ -26,6 +26,7 @@
 #include "src/tint/sem/index_accessor_expression.h"
 #include "src/tint/sem/member_accessor_expression.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/reference.h"
 #include "src/tint/utils/map.h"
 #include "src/tint/utils/unique_vector.h"
@@ -81,22 +82,22 @@
                     out_args[0] = b.AddressOf(out_args[0]);
 
                     // Replace all callsites of this stub to a call to the real builtin
-                    if (stub->builtin == sem::BuiltinType::kAtomicCompareExchangeWeak) {
+                    if (stub->builtin == builtin::Function::kAtomicCompareExchangeWeak) {
                         // atomicCompareExchangeWeak returns a struct, so insert a call to it above
                         // the current statement, and replace the current call with the struct's
                         // `old_value` member.
                         auto* block = call->Stmt()->Block()->Declaration();
                         auto old_value = b.Symbols().New("old_value");
                         auto old_value_decl = b.Decl(b.Let(
-                            old_value,
-                            b.MemberAccessor(b.Call(sem::str(stub->builtin), std::move(out_args)),
-                                             "old_value")));
+                            old_value, b.MemberAccessor(
+                                           b.Call(builtin::str(stub->builtin), std::move(out_args)),
+                                           "old_value")));
                         ctx.InsertBefore(block->statements, call->Stmt()->Declaration(),
                                          old_value_decl);
                         ctx.Replace(call->Declaration(), b.Expr(old_value));
                     } else {
                         ctx.Replace(call->Declaration(),
-                                    b.Call(sem::str(stub->builtin), std::move(out_args)));
+                                    b.Call(builtin::str(stub->builtin), std::move(out_args)));
                     }
 
                     // Keep track of this expression. We'll need to modify the root identifier /
@@ -255,7 +256,7 @@
                             ctx.Replace(assign, [=] {
                                 auto* lhs = ctx.CloneWithoutTransform(assign->lhs);
                                 auto* rhs = ctx.CloneWithoutTransform(assign->rhs);
-                                auto* call = b.Call(sem::str(sem::BuiltinType::kAtomicStore),
+                                auto* call = b.Call(builtin::str(builtin::Function::kAtomicStore),
                                                     b.AddressOf(lhs), rhs);
                                 return b.CallStmt(call);
                             });
@@ -266,7 +267,7 @@
                         if (is_ref_to_atomic_var(sem_rhs->UnwrapLoad())) {
                             ctx.Replace(assign->rhs, [=] {
                                 auto* rhs = ctx.CloneWithoutTransform(assign->rhs);
-                                return b.Call(sem::str(sem::BuiltinType::kAtomicLoad),
+                                return b.Call(builtin::str(builtin::Function::kAtomicLoad),
                                               b.AddressOf(rhs));
                             });
                             return;
@@ -278,7 +279,7 @@
                             if (is_ref_to_atomic_var(sem_init->UnwrapLoad())) {
                                 ctx.Replace(var->initializer, [=] {
                                     auto* rhs = ctx.CloneWithoutTransform(var->initializer);
-                                    return b.Call(sem::str(sem::BuiltinType::kAtomicLoad),
+                                    return b.Call(builtin::str(builtin::Function::kAtomicLoad),
                                                   b.AddressOf(rhs));
                                 });
                                 return;
@@ -293,11 +294,11 @@
 SpirvAtomic::SpirvAtomic() = default;
 SpirvAtomic::~SpirvAtomic() = default;
 
-SpirvAtomic::Stub::Stub(ProgramID pid, ast::NodeID nid, sem::BuiltinType b)
+SpirvAtomic::Stub::Stub(ProgramID pid, ast::NodeID nid, builtin::Function b)
     : Base(pid, nid, utils::Empty), builtin(b) {}
 SpirvAtomic::Stub::~Stub() = default;
 std::string SpirvAtomic::Stub::InternalName() const {
-    return "@internal(spirv-atomic " + std::string(sem::str(builtin)) + ")";
+    return "@internal(spirv-atomic " + std::string(builtin::str(builtin)) + ")";
 }
 
 const SpirvAtomic::Stub* SpirvAtomic::Stub::Clone(CloneContext* ctx) const {
diff --git a/src/tint/transform/spirv_atomic.h b/src/tint/transform/spirv_atomic.h
index 0f99dba..b524200 100644
--- a/src/tint/transform/spirv_atomic.h
+++ b/src/tint/transform/spirv_atomic.h
@@ -18,7 +18,7 @@
 #include <string>
 
 #include "src/tint/ast/internal_attribute.h"
-#include "src/tint/sem/builtin_type.h"
+#include "src/tint/builtin/function.h"
 #include "src/tint/transform/transform.h"
 
 // Forward declarations
@@ -46,7 +46,7 @@
         /// @param pid the identifier of the program that owns this node
         /// @param nid the unique node identifier
         /// @param builtin the atomic builtin this stub represents
-        Stub(ProgramID pid, ast::NodeID nid, sem::BuiltinType builtin);
+        Stub(ProgramID pid, ast::NodeID nid, builtin::Function builtin);
         /// Destructor
         ~Stub() override;
 
@@ -60,7 +60,7 @@
         const Stub* Clone(CloneContext* ctx) const override;
 
         /// The type of the intrinsic
-        const sem::BuiltinType builtin;
+        const builtin::Function builtin;
     };
 
     /// @copydoc Transform::Apply
diff --git a/src/tint/transform/spirv_atomic_test.cc b/src/tint/transform/spirv_atomic_test.cc
index 7bb9f2a..29976b5 100644
--- a/src/tint/transform/spirv_atomic_test.cc
+++ b/src/tint/transform/spirv_atomic_test.cc
@@ -36,14 +36,14 @@
 
         auto& b = parser.builder();
 
-        sem::BuiltinType two_params[] = {
-            sem::BuiltinType::kAtomicExchange, sem::BuiltinType::kAtomicAdd,
-            sem::BuiltinType::kAtomicSub,      sem::BuiltinType::kAtomicMin,
-            sem::BuiltinType::kAtomicMax,      sem::BuiltinType::kAtomicAnd,
-            sem::BuiltinType::kAtomicOr,       sem::BuiltinType::kAtomicXor,
+        builtin::Function two_params[] = {
+            builtin::Function::kAtomicExchange, builtin::Function::kAtomicAdd,
+            builtin::Function::kAtomicSub,      builtin::Function::kAtomicMin,
+            builtin::Function::kAtomicMax,      builtin::Function::kAtomicAnd,
+            builtin::Function::kAtomicOr,       builtin::Function::kAtomicXor,
         };
         for (auto& a : two_params) {
-            b.Func(std::string{"stub_"} + sem::str(a) + "_u32",
+            b.Func(std::string{"stub_"} + builtin::str(a) + "_u32",
                    utils::Vector{
                        b.Param("p0", b.ty.u32()),
                        b.Param("p1", b.ty.u32()),
@@ -55,7 +55,7 @@
                    utils::Vector{
                        b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(), a),
                    });
-            b.Func(std::string{"stub_"} + sem::str(a) + "_i32",
+            b.Func(std::string{"stub_"} + builtin::str(a) + "_i32",
                    utils::Vector{
                        b.Param("p0", b.ty.i32()),
                        b.Param("p1", b.ty.i32()),
@@ -79,7 +79,7 @@
                },
                utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
-                                                          sem::BuiltinType::kAtomicLoad),
+                                                          builtin::Function::kAtomicLoad),
                });
         b.Func("stub_atomicLoad_i32",
                utils::Vector{
@@ -91,7 +91,7 @@
                },
                utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
-                                                          sem::BuiltinType::kAtomicLoad),
+                                                          builtin::Function::kAtomicLoad),
                });
 
         b.Func("stub_atomicStore_u32",
@@ -102,7 +102,7 @@
                b.ty.void_(), utils::Empty,
                utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
-                                                          sem::BuiltinType::kAtomicStore),
+                                                          builtin::Function::kAtomicStore),
                });
         b.Func("stub_atomicStore_i32",
                utils::Vector{
@@ -112,7 +112,7 @@
                b.ty.void_(), utils::Empty,
                utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
-                                                          sem::BuiltinType::kAtomicStore),
+                                                          builtin::Function::kAtomicStore),
                });
 
         b.Func("stub_atomic_compare_exchange_weak_u32",
@@ -127,7 +127,7 @@
                },
                utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(
-                       b.ID(), b.AllocateNodeID(), sem::BuiltinType::kAtomicCompareExchangeWeak),
+                       b.ID(), b.AllocateNodeID(), builtin::Function::kAtomicCompareExchangeWeak),
                });
         b.Func("stub_atomic_compare_exchange_weak_i32",
                utils::Vector{b.Param("p0", b.ty.i32()), b.Param("p1", b.ty.i32()),
@@ -138,7 +138,7 @@
                },
                utils::Vector{
                    b.ASTNodes().Create<SpirvAtomic::Stub>(
-                       b.ID(), b.AllocateNodeID(), sem::BuiltinType::kAtomicCompareExchangeWeak),
+                       b.ID(), b.AllocateNodeID(), builtin::Function::kAtomicCompareExchangeWeak),
                });
 
         // Keep this pointer alive after Transform() returns
diff --git a/src/tint/transform/std140.cc b/src/tint/transform/std140.cc
index 7a9cffc..bfd8c80 100644
--- a/src/tint/transform/std140.cc
+++ b/src/tint/transform/std140.cc
@@ -25,6 +25,7 @@
 #include "src/tint/sem/module.h"
 #include "src/tint/sem/struct.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/utils/compiler_macros.h"
 #include "src/tint/utils/hashmap.h"
 #include "src/tint/utils/transform.h"
diff --git a/src/tint/transform/substitute_override.cc b/src/tint/transform/substitute_override.cc
index 2d6b995..04a9da0 100644
--- a/src/tint/transform/substitute_override.cc
+++ b/src/tint/transform/substitute_override.cc
@@ -17,10 +17,12 @@
 #include <functional>
 #include <utility>
 
+#include "src/tint/builtin/function.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/builtin.h"
 #include "src/tint/sem/index_accessor_expression.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::SubstituteOverride);
 TINT_INSTANTIATE_TYPEINFO(tint::transform::SubstituteOverride::Config);
@@ -105,7 +107,7 @@
                 if (auto* access = sem->UnwrapMaterialize()->As<sem::IndexAccessorExpression>()) {
                     if (access->Object()->UnwrapMaterialize()->Type()->HoldsAbstract() &&
                         access->Index()->Stage() == sem::EvaluationStage::kOverride) {
-                        auto* obj = b.Call(sem::str(sem::BuiltinType::kTintMaterialize),
+                        auto* obj = b.Call(builtin::str(builtin::Function::kTintMaterialize),
                                            ctx.Clone(expr->object));
                         return b.IndexAccessor(obj, ctx.Clone(expr->index));
                     }
diff --git a/src/tint/transform/texture_1d_to_2d.cc b/src/tint/transform/texture_1d_to_2d.cc
index d1caaab..7c48db5 100644
--- a/src/tint/transform/texture_1d_to_2d.cc
+++ b/src/tint/transform/texture_1d_to_2d.cc
@@ -20,6 +20,7 @@
 #include "src/tint/sem/function.h"
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/type_expression.h"
+#include "src/tint/switch.h"
 #include "src/tint/type/texture_dimension.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::Texture1DTo2D);
@@ -137,7 +138,7 @@
                 return nullptr;
             }
 
-            if (builtin->Type() == sem::BuiltinType::kTextureDimensions) {
+            if (builtin->Type() == builtin::Function::kTextureDimensions) {
                 // If this textureDimensions() call is in a CallStatement, we can leave it
                 // unmodified since the return value will be dropped on the floor anyway.
                 if (call->Stmt()->Declaration()->Is<ast::CallStatement>()) {
diff --git a/src/tint/transform/unshadow.cc b/src/tint/transform/unshadow.cc
index 8f79544..9639cfc 100644
--- a/src/tint/transform/unshadow.cc
+++ b/src/tint/transform/unshadow.cc
@@ -23,6 +23,7 @@
 #include "src/tint/sem/function.h"
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::Unshadow);
 
diff --git a/src/tint/transform/utils/get_insertion_point.cc b/src/tint/transform/utils/get_insertion_point.cc
index d10d134..bce7a7e 100644
--- a/src/tint/transform/utils/get_insertion_point.cc
+++ b/src/tint/transform/utils/get_insertion_point.cc
@@ -16,6 +16,7 @@
 #include "src/tint/debug.h"
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/sem/for_loop_statement.h"
+#include "src/tint/switch.h"
 
 namespace tint::transform::utils {
 
diff --git a/src/tint/transform/vertex_pulling.cc b/src/tint/transform/vertex_pulling.cc
index e07dcfe..d887198 100644
--- a/src/tint/transform/vertex_pulling.cc
+++ b/src/tint/transform/vertex_pulling.cc
@@ -23,6 +23,7 @@
 #include "src/tint/builtin/builtin_value.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/utils/compiler_macros.h"
 #include "src/tint/utils/map.h"
 #include "src/tint/utils/math.h"
diff --git a/src/tint/type/type.cc b/src/tint/type/type.cc
index 6163a9a..7ed62fa 100644
--- a/src/tint/type/type.cc
+++ b/src/tint/type/type.cc
@@ -14,6 +14,7 @@
 
 #include "src/tint/type/type.h"
 
+#include "src/tint/switch.h"
 #include "src/tint/type/abstract_float.h"
 #include "src/tint/type/abstract_int.h"
 #include "src/tint/type/array.h"
diff --git a/src/tint/utils/slice.h b/src/tint/utils/slice.h
index 719a53e..325c470 100644
--- a/src/tint/utils/slice.h
+++ b/src/tint/utils/slice.h
@@ -20,6 +20,7 @@
 
 #include "src/tint/castable.h"
 #include "src/tint/traits.h"
+#include "src/tint/utils/bitcast.h"
 
 namespace tint::utils {
 
diff --git a/src/tint/writer/append_vector.cc b/src/tint/writer/append_vector.cc
index 98d9798..b66f9fd 100644
--- a/src/tint/writer/append_vector.cc
+++ b/src/tint/writer/append_vector.cc
@@ -21,6 +21,7 @@
 #include "src/tint/sem/value_constructor.h"
 #include "src/tint/sem/value_conversion.h"
 #include "src/tint/sem/value_expression.h"
+#include "src/tint/switch.h"
 #include "src/tint/utils/transform.h"
 
 using namespace tint::number_suffixes;  // NOLINT
diff --git a/src/tint/writer/array_length_from_uniform_options.cc b/src/tint/writer/array_length_from_uniform_options.cc
index 7fd6e63..275867f 100644
--- a/src/tint/writer/array_length_from_uniform_options.cc
+++ b/src/tint/writer/array_length_from_uniform_options.cc
@@ -17,11 +17,15 @@
 namespace tint::writer {
 
 ArrayLengthFromUniformOptions::ArrayLengthFromUniformOptions() = default;
+
 ArrayLengthFromUniformOptions::~ArrayLengthFromUniformOptions() = default;
+
 ArrayLengthFromUniformOptions::ArrayLengthFromUniformOptions(const ArrayLengthFromUniformOptions&) =
     default;
+
 ArrayLengthFromUniformOptions& ArrayLengthFromUniformOptions::operator=(
     const ArrayLengthFromUniformOptions&) = default;
+
 ArrayLengthFromUniformOptions::ArrayLengthFromUniformOptions(ArrayLengthFromUniformOptions&&) =
     default;
 
diff --git a/src/tint/writer/array_length_from_uniform_options.h b/src/tint/writer/array_length_from_uniform_options.h
index 41d873e..b8b304a 100644
--- a/src/tint/writer/array_length_from_uniform_options.h
+++ b/src/tint/writer/array_length_from_uniform_options.h
@@ -17,7 +17,7 @@
 
 #include <unordered_map>
 
-#include "src/tint/sem/binding_point.h"
+#include "src/tint/writer/binding_point.h"
 
 namespace tint::writer {
 
@@ -38,10 +38,10 @@
 
     /// The binding point to use to generate a uniform buffer from which to read
     /// buffer sizes.
-    sem::BindingPoint ubo_binding;
+    BindingPoint ubo_binding;
     /// The mapping from storage buffer binding points to the index into the
     /// uniform buffer where the length of the buffer is stored.
-    std::unordered_map<sem::BindingPoint, uint32_t> bindpoint_to_size_index;
+    std::unordered_map<BindingPoint, uint32_t> bindpoint_to_size_index;
 
     /// Reflect the fields of this class so that it can be used by tint::ForeachField()
     TINT_REFLECT(ubo_binding, bindpoint_to_size_index);
diff --git a/src/tint/writer/generate_external_texture_bindings.h b/src/tint/writer/binding_point.h
similarity index 60%
rename from src/tint/writer/generate_external_texture_bindings.h
rename to src/tint/writer/binding_point.h
index 8d0aad9..796c54a 100644
--- a/src/tint/writer/generate_external_texture_bindings.h
+++ b/src/tint/writer/binding_point.h
@@ -1,4 +1,4 @@
-// Copyright 2022 The Tint Authors.
+// Copyright 2023 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.
@@ -12,16 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SRC_TINT_WRITER_GENERATE_EXTERNAL_TEXTURE_BINDINGS_H_
-#define SRC_TINT_WRITER_GENERATE_EXTERNAL_TEXTURE_BINDINGS_H_
+#ifndef SRC_TINT_WRITER_BINDING_POINT_H_
+#define SRC_TINT_WRITER_BINDING_POINT_H_
 
-#include "src/tint/transform/multiplanar_external_texture.h"
+#include "src/tint/sem/binding_point.h"
 
 namespace tint::writer {
 
-transform::MultiplanarExternalTexture::BindingsMap GenerateExternalTextureBindings(
-    const Program* program);
+/// BindingPoint is an alias to sem::BindingPoint
+using BindingPoint = sem::BindingPoint;
 
 }  // namespace tint::writer
 
-#endif  // SRC_TINT_WRITER_GENERATE_EXTERNAL_TEXTURE_BINDINGS_H_
+#endif  // SRC_TINT_WRITER_BINDING_POINT_H_
diff --git a/src/tint/writer/check_supported_extensions.cc b/src/tint/writer/check_supported_extensions.cc
index bde86a5..88ddcdd 100644
--- a/src/tint/writer/check_supported_extensions.cc
+++ b/src/tint/writer/check_supported_extensions.cc
@@ -33,13 +33,14 @@
     }
 
     for (auto* enable : module.Enables()) {
-        auto ext = enable->extension;
-        if (!set.Contains(ext)) {
-            diags.add_error(diag::System::Writer,
-                            std::string(writer_name) + " backend does not support extension '" +
-                                utils::ToString(ext) + "'",
-                            enable->source);
-            return false;
+        for (auto* ext : enable->extensions) {
+            if (!set.Contains(ext->name)) {
+                diags.add_error(diag::System::Writer,
+                                std::string(writer_name) + " backend does not support extension '" +
+                                    utils::ToString(ext->name) + "'",
+                                ext->source);
+                return false;
+            }
         }
     }
     return true;
diff --git a/src/tint/writer/external_texture_options.cc b/src/tint/writer/external_texture_options.cc
new file mode 100644
index 0000000..e5ea26f
--- /dev/null
+++ b/src/tint/writer/external_texture_options.cc
@@ -0,0 +1,29 @@
+// Copyright 2023 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/tint/writer/external_texture_options.h"
+
+namespace tint::writer {
+
+ExternalTextureOptions::ExternalTextureOptions() = default;
+
+ExternalTextureOptions::~ExternalTextureOptions() = default;
+
+ExternalTextureOptions::ExternalTextureOptions(const ExternalTextureOptions&) = default;
+
+ExternalTextureOptions& ExternalTextureOptions::operator=(const ExternalTextureOptions&) = default;
+
+ExternalTextureOptions::ExternalTextureOptions(ExternalTextureOptions&&) = default;
+
+}  // namespace tint::writer
diff --git a/src/tint/writer/external_texture_options.h b/src/tint/writer/external_texture_options.h
new file mode 100644
index 0000000..926eabd
--- /dev/null
+++ b/src/tint/writer/external_texture_options.h
@@ -0,0 +1,57 @@
+// Copyright 2023 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_TINT_WRITER_EXTERNAL_TEXTURE_OPTIONS_H_
+#define SRC_TINT_WRITER_EXTERNAL_TEXTURE_OPTIONS_H_
+
+#include <unordered_map>
+
+#include "src/tint/sem/external_texture.h"
+
+namespace tint::writer {
+
+/// Options used to specify mappings of binding points for external textures.
+class ExternalTextureOptions {
+  public:
+    /// This struct identifies the binding groups and locations for new bindings to
+    /// use when transforming a texture_external instance.
+    using BindingPoints = sem::external_texture::BindingPoints;
+
+    /// BindingsMap is a map where the key is the binding location of a
+    /// texture_external and the value is a struct containing the desired
+    /// locations for new bindings expanded from the texture_external instance.
+    using BindingsMap = sem::external_texture::BindingsMap;
+
+    /// Constructor
+    ExternalTextureOptions();
+    /// Destructor
+    ~ExternalTextureOptions();
+    /// Copy constructor
+    ExternalTextureOptions(const ExternalTextureOptions&);
+    /// Copy assignment
+    /// @returns this ExternalTextureOptions
+    ExternalTextureOptions& operator=(const ExternalTextureOptions&);
+    /// Move constructor
+    ExternalTextureOptions(ExternalTextureOptions&&);
+
+    /// A map of new binding points to use.
+    BindingsMap bindings_map;
+
+    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
+    TINT_REFLECT(bindings_map);
+};
+
+}  // namespace tint::writer
+
+#endif  // SRC_TINT_WRITER_EXTERNAL_TEXTURE_OPTIONS_H_
diff --git a/src/tint/writer/flatten_bindings.cc b/src/tint/writer/flatten_bindings.cc
index bedec75..3b5b2ad 100644
--- a/src/tint/writer/flatten_bindings.cc
+++ b/src/tint/writer/flatten_bindings.cc
@@ -19,11 +19,12 @@
 #include "src/tint/inspector/inspector.h"
 #include "src/tint/transform/binding_remapper.h"
 #include "src/tint/transform/manager.h"
+#include "src/tint/writer/binding_point.h"
 
 namespace tint::writer {
+
 std::optional<Program> FlattenBindings(const Program* program) {
     // TODO(crbug.com/tint/1101): Make this more robust for multiple entry points.
-    using BindingPoint = tint::transform::BindingPoint;
     tint::transform::BindingRemapper::BindingPoints binding_points;
     uint32_t next_buffer_idx = 0;
     uint32_t next_sampler_idx = 0;
@@ -76,4 +77,5 @@
 
     return {};
 }
+
 }  // namespace tint::writer
diff --git a/src/tint/writer/glsl/generator.h b/src/tint/writer/glsl/generator.h
index 2bb4b03..ef7b65b 100644
--- a/src/tint/writer/glsl/generator.h
+++ b/src/tint/writer/glsl/generator.h
@@ -25,6 +25,7 @@
 #include "src/tint/builtin/access.h"
 #include "src/tint/sem/binding_point.h"
 #include "src/tint/sem/sampler_texture_pair.h"
+#include "src/tint/writer/external_texture_options.h"
 #include "src/tint/writer/glsl/version.h"
 #include "src/tint/writer/text.h"
 
@@ -73,8 +74,8 @@
     /// Set to `true` to disable workgroup memory zero initialization
     bool disable_workgroup_init = false;
 
-    /// Set to 'true' to generates binding mappings for external textures
-    bool generate_external_texture_bindings = false;
+    /// Options used in the binding mappings for external textures
+    ExternalTextureOptions external_texture_options = {};
 
     /// The GLSL version to emit
     Version version;
@@ -83,7 +84,7 @@
     TINT_REFLECT(disable_robustness,
                  allow_collisions,
                  disable_workgroup_init,
-                 generate_external_texture_bindings,
+                 external_texture_options,
                  version);
 };
 
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 02c1bac..311005b 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -17,6 +17,7 @@
 #include <algorithm>
 #include <cmath>
 #include <iomanip>
+#include <limits>
 #include <set>
 #include <utility>
 #include <vector>
@@ -39,6 +40,7 @@
 #include "src/tint/sem/value_constructor.h"
 #include "src/tint/sem/value_conversion.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/transform/add_block_attribute.h"
 #include "src/tint/transform/add_empty_entry_point.h"
 #include "src/tint/transform/binding_remapper.h"
@@ -51,6 +53,7 @@
 #include "src/tint/transform/disable_uniformity_analysis.h"
 #include "src/tint/transform/expand_compound_assignment.h"
 #include "src/tint/transform/manager.h"
+#include "src/tint/transform/multiplanar_external_texture.h"
 #include "src/tint/transform/pad_structs.h"
 #include "src/tint/transform/preserve_padding.h"
 #include "src/tint/transform/promote_initializers_to_let.h"
@@ -79,12 +82,18 @@
 #include "src/tint/utils/string_stream.h"
 #include "src/tint/writer/append_vector.h"
 #include "src/tint/writer/float_to_string.h"
-#include "src/tint/writer/generate_external_texture_bindings.h"
 
 using namespace tint::number_suffixes;  // NOLINT
 
+namespace tint::writer::glsl {
 namespace {
 
+const char kTempNamePrefix[] = "tint_tmp";
+
+bool last_is_break(const ast::BlockStatement* stmts) {
+    return IsAnyOf<ast::BreakStatement>(stmts->Last());
+}
+
 bool IsRelational(tint::ast::BinaryOp op) {
     return op == tint::ast::BinaryOp::kEqual || op == tint::ast::BinaryOp::kNotEqual ||
            op == tint::ast::BinaryOp::kLessThan || op == tint::ast::BinaryOp::kGreaterThan ||
@@ -102,15 +111,15 @@
     }
 }
 
-}  // namespace
-
-namespace tint::writer::glsl {
-namespace {
-
-const char kTempNamePrefix[] = "tint_tmp";
-
-bool last_is_break(const ast::BlockStatement* stmts) {
-    return IsAnyOf<ast::BreakStatement>(stmts->Last());
+void PrintI32(utils::StringStream& out, int32_t value) {
+    // GLSL parses `-2147483648` as a unary minus and `2147483648` as separate tokens, and the
+    // latter doesn't fit into an (32-bit) `int`. Emit `(-2147483647 - 1)` instead, which ensures
+    // the expression type is `int`.
+    if (auto int_min = std::numeric_limits<int32_t>::min(); value == int_min) {
+        out << "(" << int_min + 1 << " - 1)";
+    } else {
+        out << value;
+    }
 }
 
 void PrintF32(utils::StringStream& out, float value) {
@@ -171,10 +180,10 @@
         manager.Add<transform::Robustness>();
     }
 
-    if (options.generate_external_texture_bindings) {
+    if (!options.external_texture_options.bindings_map.empty()) {
         // Note: it is more efficient for MultiplanarExternalTexture to come after Robustness
-        auto new_bindings_map = writer::GenerateExternalTextureBindings(in);
-        data.Add<transform::MultiplanarExternalTexture::NewBindingPoints>(new_bindings_map);
+        data.Add<transform::MultiplanarExternalTexture::NewBindingPoints>(
+            options.external_texture_options.bindings_map);
         manager.Add<transform::MultiplanarExternalTexture>();
     }
 
@@ -184,6 +193,7 @@
         polyfills.atanh = transform::BuiltinPolyfill::Level::kRangeCheck;
         polyfills.bgra8unorm = true;
         polyfills.bitshift_modulo = true;
+        polyfills.conv_f32_to_iu32 = true;
         polyfills.count_leading_zeros = true;
         polyfills.count_trailing_zeros = true;
         polyfills.extract_bits = transform::BuiltinPolyfill::Level::kClampParameters;
@@ -325,7 +335,7 @@
     }
 
     if (version_.IsES() && requires_default_precision_qualifier_) {
-        current_buffer_->Insert("precision mediump float;", helpers_insertion_point++, indent);
+        current_buffer_->Insert("precision highp float;", helpers_insertion_point++, indent);
     }
 
     if (!helpers_.lines.empty()) {
@@ -337,10 +347,10 @@
     return true;
 }
 
-bool GeneratorImpl::RecordExtension(const ast::Enable* ext) {
+bool GeneratorImpl::RecordExtension(const ast::Enable* enable) {
     // Deal with extension node here, recording it within the generator for later emition.
 
-    if (ext->extension == builtin::Extension::kF16) {
+    if (enable->HasExtension(builtin::Extension::kF16)) {
         requires_f16_extension_ = true;
     }
 
@@ -768,48 +778,49 @@
     if (builtin->IsTexture()) {
         return EmitTextureCall(out, call, builtin);
     }
-    if (builtin->Type() == sem::BuiltinType::kCountOneBits) {
+    if (builtin->Type() == builtin::Function::kCountOneBits) {
         return EmitCountOneBitsCall(out, expr);
     }
-    if (builtin->Type() == sem::BuiltinType::kSelect) {
-        return EmitSelectCall(out, expr);
+    if (builtin->Type() == builtin::Function::kSelect) {
+        return EmitSelectCall(out, expr, builtin);
     }
-    if (builtin->Type() == sem::BuiltinType::kDot) {
+    if (builtin->Type() == builtin::Function::kDot) {
         return EmitDotCall(out, expr, builtin);
     }
-    if (builtin->Type() == sem::BuiltinType::kModf) {
+    if (builtin->Type() == builtin::Function::kModf) {
         return EmitModfCall(out, expr, builtin);
     }
-    if (builtin->Type() == sem::BuiltinType::kFrexp) {
+    if (builtin->Type() == builtin::Function::kFrexp) {
         return EmitFrexpCall(out, expr, builtin);
     }
-    if (builtin->Type() == sem::BuiltinType::kDegrees) {
+    if (builtin->Type() == builtin::Function::kDegrees) {
         return EmitDegreesCall(out, expr, builtin);
     }
-    if (builtin->Type() == sem::BuiltinType::kRadians) {
+    if (builtin->Type() == builtin::Function::kRadians) {
         return EmitRadiansCall(out, expr, builtin);
     }
-    if (builtin->Type() == sem::BuiltinType::kQuantizeToF16) {
+    if (builtin->Type() == builtin::Function::kQuantizeToF16) {
         return EmitQuantizeToF16Call(out, expr, builtin);
     }
-    if (builtin->Type() == sem::BuiltinType::kArrayLength) {
+    if (builtin->Type() == builtin::Function::kArrayLength) {
         return EmitArrayLength(out, expr);
     }
-    if (builtin->Type() == sem::BuiltinType::kExtractBits) {
+    if (builtin->Type() == builtin::Function::kExtractBits) {
         return EmitExtractBits(out, expr);
     }
-    if (builtin->Type() == sem::BuiltinType::kInsertBits) {
+    if (builtin->Type() == builtin::Function::kInsertBits) {
         return EmitInsertBits(out, expr);
     }
-    if (builtin->Type() == sem::BuiltinType::kFma && version_.IsES()) {
+    if (builtin->Type() == builtin::Function::kFma && version_.IsES()) {
         return EmitEmulatedFMA(out, expr);
     }
-    if (builtin->Type() == sem::BuiltinType::kAbs &&
+    if (builtin->Type() == builtin::Function::kAbs &&
         TypeOf(expr->args[0])->UnwrapRef()->is_unsigned_integer_scalar_or_vector()) {
         // GLSL does not support abs() on unsigned arguments. However, it's a no-op.
         return EmitExpression(out, expr->args[0]);
     }
-    if ((builtin->Type() == sem::BuiltinType::kAny || builtin->Type() == sem::BuiltinType::kAll) &&
+    if ((builtin->Type() == builtin::Function::kAny ||
+         builtin->Type() == builtin::Function::kAll) &&
         TypeOf(expr->args[0])->UnwrapRef()->is_scalar()) {
         // GLSL does not support any() or all() on scalar arguments. It's a no-op.
         return EmitExpression(out, expr->args[0]);
@@ -911,7 +922,7 @@
     };
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kAtomicLoad: {
+        case builtin::Function::kAtomicLoad: {
             // GLSL does not have an atomicLoad, so we emulate it with
             // atomicOr using 0 as the OR value
             out << "atomicOr";
@@ -927,7 +938,7 @@
             }
             return true;
         }
-        case sem::BuiltinType::kAtomicCompareExchangeWeak: {
+        case builtin::Function::kAtomicCompareExchangeWeak: {
             if (!EmitStructType(&helpers_, builtin->ReturnType()->As<sem::Struct>())) {
                 return false;
             }
@@ -978,27 +989,27 @@
             return true;
         }
 
-        case sem::BuiltinType::kAtomicAdd:
-        case sem::BuiltinType::kAtomicSub:
+        case builtin::Function::kAtomicAdd:
+        case builtin::Function::kAtomicSub:
             return call("atomicAdd");
 
-        case sem::BuiltinType::kAtomicMax:
+        case builtin::Function::kAtomicMax:
             return call("atomicMax");
 
-        case sem::BuiltinType::kAtomicMin:
+        case builtin::Function::kAtomicMin:
             return call("atomicMin");
 
-        case sem::BuiltinType::kAtomicAnd:
+        case builtin::Function::kAtomicAnd:
             return call("atomicAnd");
 
-        case sem::BuiltinType::kAtomicOr:
+        case builtin::Function::kAtomicOr:
             return call("atomicOr");
 
-        case sem::BuiltinType::kAtomicXor:
+        case builtin::Function::kAtomicXor:
             return call("atomicXor");
 
-        case sem::BuiltinType::kAtomicExchange:
-        case sem::BuiltinType::kAtomicStore:
+        case builtin::Function::kAtomicExchange:
+        case builtin::Function::kAtomicStore:
             // GLSL does not have an atomicStore, so we emulate it with
             // atomicExchange.
             return call("atomicExchange");
@@ -1092,28 +1103,39 @@
     return true;
 }
 
-bool GeneratorImpl::EmitSelectCall(utils::StringStream& out, const ast::CallExpression* expr) {
+bool GeneratorImpl::EmitSelectCall(utils::StringStream& out,
+                                   const ast::CallExpression* expr,
+                                   const sem::Builtin* builtin) {
+    // GLSL does not support ternary expressions with a bool vector conditional,
+    // so polyfill with a helper.
+    if (auto* vec = builtin->Parameters()[2]->Type()->As<type::Vector>()) {
+        return CallBuiltinHelper(
+            out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
+                auto l = line(b);
+                l << "  return ";
+                if (!EmitType(l, builtin->ReturnType(), builtin::AddressSpace::kUndefined,
+                              builtin::Access::kUndefined, "")) {
+                    return false;
+                }
+                {
+                    ScopedParen sp(l);
+                    for (uint32_t i = 0; i < vec->Width(); i++) {
+                        if (i > 0) {
+                            l << ", ";
+                        }
+                        l << params[2] << "[" << i << "] ? " << params[1] << "[" << i
+                          << "] : " << params[0] << "[" << i << "]";
+                    }
+                }
+                l << ";";
+                return true;
+            });
+    }
+
     auto* expr_false = expr->args[0];
     auto* expr_true = expr->args[1];
     auto* expr_cond = expr->args[2];
-    // GLSL does not support ternary expressions with a bool vector conditional,
-    // but it does support mix() with same.
-    if (TypeOf(expr_cond)->UnwrapRef()->is_bool_vector()) {
-        out << "mix(";
-        if (!EmitExpression(out, expr_false)) {
-            return false;
-        }
-        out << ", ";
-        if (!EmitExpression(out, expr_true)) {
-            return false;
-        }
-        out << ", ";
-        if (!EmitExpression(out, expr_cond)) {
-            return false;
-        }
-        out << ")";
-        return true;
-    }
+
     ScopedParen paren(out);
     if (!EmitExpression(out, expr_cond)) {
         return false;
@@ -1320,13 +1342,13 @@
 bool GeneratorImpl::EmitBarrierCall(utils::StringStream& out, const sem::Builtin* builtin) {
     // TODO(crbug.com/tint/661): Combine sequential barriers to a single
     // instruction.
-    if (builtin->Type() == sem::BuiltinType::kWorkgroupBarrier) {
+    if (builtin->Type() == builtin::Function::kWorkgroupBarrier) {
         out << "barrier()";
-    } else if (builtin->Type() == sem::BuiltinType::kStorageBarrier) {
+    } else if (builtin->Type() == builtin::Function::kStorageBarrier) {
         out << "{ barrier(); memoryBarrierBuffer(); }";
     } else {
         TINT_UNREACHABLE(Writer, diagnostics_)
-            << "unexpected barrier builtin type " << sem::str(builtin->Type());
+            << "unexpected barrier builtin type " << builtin::str(builtin->Type());
         return false;
     }
     return true;
@@ -1396,7 +1418,7 @@
     };
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kTextureDimensions: {
+        case builtin::Function::kTextureDimensions: {
             // textureDimensions() returns an unsigned scalar / vector in WGSL.
             // textureSize() / imageSize() returns a signed scalar / vector in GLSL.
             // Cast.
@@ -1435,7 +1457,7 @@
             }
             return true;
         }
-        case sem::BuiltinType::kTextureNumLayers: {
+        case builtin::Function::kTextureNumLayers: {
             // textureNumLayers() returns an unsigned scalar in WGSL.
             // textureSize() / imageSize() returns a signed scalar / vector in GLSL.
             // Cast.
@@ -1469,7 +1491,7 @@
             out << ").z";
             return true;
         }
-        case sem::BuiltinType::kTextureNumLevels: {
+        case builtin::Function::kTextureNumLevels: {
             // textureNumLevels() returns an unsigned scalar in WGSL.
             // textureQueryLevels() returns a signed scalar in GLSL.
             // Cast.
@@ -1483,7 +1505,7 @@
             out << ")";
             return true;
         }
-        case sem::BuiltinType::kTextureNumSamples: {
+        case builtin::Function::kTextureNumSamples: {
             // textureNumSamples() returns an unsigned scalar in WGSL.
             // textureSamples() returns a signed scalar in GLSL.
             // Cast.
@@ -1506,36 +1528,36 @@
     bool is_depth = texture_type->Is<type::DepthTexture>();
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kTextureSample:
-        case sem::BuiltinType::kTextureSampleBias:
+        case builtin::Function::kTextureSample:
+        case builtin::Function::kTextureSampleBias:
             out << "texture";
             if (is_depth) {
                 glsl_ret_width = 1u;
             }
             break;
-        case sem::BuiltinType::kTextureSampleLevel:
+        case builtin::Function::kTextureSampleLevel:
             out << "textureLod";
             if (is_depth) {
                 glsl_ret_width = 1u;
             }
             break;
-        case sem::BuiltinType::kTextureGather:
-        case sem::BuiltinType::kTextureGatherCompare:
+        case builtin::Function::kTextureGather:
+        case builtin::Function::kTextureGatherCompare:
             out << "textureGather";
             append_depth_ref_to_coords = false;
             break;
-        case sem::BuiltinType::kTextureSampleGrad:
+        case builtin::Function::kTextureSampleGrad:
             out << "textureGrad";
             break;
-        case sem::BuiltinType::kTextureSampleCompare:
-        case sem::BuiltinType::kTextureSampleCompareLevel:
+        case builtin::Function::kTextureSampleCompare:
+        case builtin::Function::kTextureSampleCompareLevel:
             out << "texture";
             glsl_ret_width = 1;
             break;
-        case sem::BuiltinType::kTextureLoad:
+        case builtin::Function::kTextureLoad:
             out << "texelFetch";
             break;
-        case sem::BuiltinType::kTextureStore:
+        case builtin::Function::kTextureStore:
             out << "imageStore";
             break;
         default:
@@ -1614,7 +1636,7 @@
     }
 
     // GLSL's textureGather always requires a refZ parameter.
-    if (is_depth && builtin->Type() == sem::BuiltinType::kTextureGather) {
+    if (is_depth && builtin->Type() == builtin::Function::kTextureGather) {
         out << ", 0.0";
     }
 
@@ -1625,7 +1647,7 @@
             if (!EmitExpression(out, e)) {
                 return false;
             }
-        } else if (builtin->Type() == sem::BuiltinType::kTextureSample) {
+        } else if (builtin->Type() == builtin::Function::kTextureSample) {
             out << ", 0.0f";
         }
     }
@@ -1669,114 +1691,114 @@
 
 std::string GeneratorImpl::generate_builtin_name(const sem::Builtin* builtin) {
     switch (builtin->Type()) {
-        case sem::BuiltinType::kAbs:
-        case sem::BuiltinType::kAcos:
-        case sem::BuiltinType::kAcosh:
-        case sem::BuiltinType::kAll:
-        case sem::BuiltinType::kAny:
-        case sem::BuiltinType::kAsin:
-        case sem::BuiltinType::kAsinh:
-        case sem::BuiltinType::kAtan:
-        case sem::BuiltinType::kAtanh:
-        case sem::BuiltinType::kCeil:
-        case sem::BuiltinType::kClamp:
-        case sem::BuiltinType::kCos:
-        case sem::BuiltinType::kCosh:
-        case sem::BuiltinType::kCross:
-        case sem::BuiltinType::kDeterminant:
-        case sem::BuiltinType::kDistance:
-        case sem::BuiltinType::kDot:
-        case sem::BuiltinType::kExp:
-        case sem::BuiltinType::kExp2:
-        case sem::BuiltinType::kFloor:
-        case sem::BuiltinType::kFrexp:
-        case sem::BuiltinType::kLdexp:
-        case sem::BuiltinType::kLength:
-        case sem::BuiltinType::kLog:
-        case sem::BuiltinType::kLog2:
-        case sem::BuiltinType::kMax:
-        case sem::BuiltinType::kMin:
-        case sem::BuiltinType::kModf:
-        case sem::BuiltinType::kNormalize:
-        case sem::BuiltinType::kPow:
-        case sem::BuiltinType::kReflect:
-        case sem::BuiltinType::kRefract:
-        case sem::BuiltinType::kRound:
-        case sem::BuiltinType::kSign:
-        case sem::BuiltinType::kSin:
-        case sem::BuiltinType::kSinh:
-        case sem::BuiltinType::kSqrt:
-        case sem::BuiltinType::kStep:
-        case sem::BuiltinType::kTan:
-        case sem::BuiltinType::kTanh:
-        case sem::BuiltinType::kTranspose:
-        case sem::BuiltinType::kTrunc:
+        case builtin::Function::kAbs:
+        case builtin::Function::kAcos:
+        case builtin::Function::kAcosh:
+        case builtin::Function::kAll:
+        case builtin::Function::kAny:
+        case builtin::Function::kAsin:
+        case builtin::Function::kAsinh:
+        case builtin::Function::kAtan:
+        case builtin::Function::kAtanh:
+        case builtin::Function::kCeil:
+        case builtin::Function::kClamp:
+        case builtin::Function::kCos:
+        case builtin::Function::kCosh:
+        case builtin::Function::kCross:
+        case builtin::Function::kDeterminant:
+        case builtin::Function::kDistance:
+        case builtin::Function::kDot:
+        case builtin::Function::kExp:
+        case builtin::Function::kExp2:
+        case builtin::Function::kFloor:
+        case builtin::Function::kFrexp:
+        case builtin::Function::kLdexp:
+        case builtin::Function::kLength:
+        case builtin::Function::kLog:
+        case builtin::Function::kLog2:
+        case builtin::Function::kMax:
+        case builtin::Function::kMin:
+        case builtin::Function::kModf:
+        case builtin::Function::kNormalize:
+        case builtin::Function::kPow:
+        case builtin::Function::kReflect:
+        case builtin::Function::kRefract:
+        case builtin::Function::kRound:
+        case builtin::Function::kSign:
+        case builtin::Function::kSin:
+        case builtin::Function::kSinh:
+        case builtin::Function::kSqrt:
+        case builtin::Function::kStep:
+        case builtin::Function::kTan:
+        case builtin::Function::kTanh:
+        case builtin::Function::kTranspose:
+        case builtin::Function::kTrunc:
             return builtin->str();
-        case sem::BuiltinType::kAtan2:
+        case builtin::Function::kAtan2:
             return "atan";
-        case sem::BuiltinType::kCountOneBits:
+        case builtin::Function::kCountOneBits:
             return "bitCount";
-        case sem::BuiltinType::kDpdx:
+        case builtin::Function::kDpdx:
             return "dFdx";
-        case sem::BuiltinType::kDpdxCoarse:
+        case builtin::Function::kDpdxCoarse:
             if (version_.IsES()) {
                 return "dFdx";
             }
             return "dFdxCoarse";
-        case sem::BuiltinType::kDpdxFine:
+        case builtin::Function::kDpdxFine:
             if (version_.IsES()) {
                 return "dFdx";
             }
             return "dFdxFine";
-        case sem::BuiltinType::kDpdy:
+        case builtin::Function::kDpdy:
             return "dFdy";
-        case sem::BuiltinType::kDpdyCoarse:
+        case builtin::Function::kDpdyCoarse:
             if (version_.IsES()) {
                 return "dFdy";
             }
             return "dFdyCoarse";
-        case sem::BuiltinType::kDpdyFine:
+        case builtin::Function::kDpdyFine:
             if (version_.IsES()) {
                 return "dFdy";
             }
             return "dFdyFine";
-        case sem::BuiltinType::kFaceForward:
+        case builtin::Function::kFaceForward:
             return "faceforward";
-        case sem::BuiltinType::kFract:
+        case builtin::Function::kFract:
             return "fract";
-        case sem::BuiltinType::kFma:
+        case builtin::Function::kFma:
             return "fma";
-        case sem::BuiltinType::kFwidth:
-        case sem::BuiltinType::kFwidthCoarse:
-        case sem::BuiltinType::kFwidthFine:
+        case builtin::Function::kFwidth:
+        case builtin::Function::kFwidthCoarse:
+        case builtin::Function::kFwidthFine:
             return "fwidth";
-        case sem::BuiltinType::kInverseSqrt:
+        case builtin::Function::kInverseSqrt:
             return "inversesqrt";
-        case sem::BuiltinType::kMix:
+        case builtin::Function::kMix:
             return "mix";
-        case sem::BuiltinType::kPack2X16Float:
+        case builtin::Function::kPack2X16Float:
             return "packHalf2x16";
-        case sem::BuiltinType::kPack2X16Snorm:
+        case builtin::Function::kPack2X16Snorm:
             return "packSnorm2x16";
-        case sem::BuiltinType::kPack2X16Unorm:
+        case builtin::Function::kPack2X16Unorm:
             return "packUnorm2x16";
-        case sem::BuiltinType::kPack4X8Snorm:
+        case builtin::Function::kPack4X8Snorm:
             return "packSnorm4x8";
-        case sem::BuiltinType::kPack4X8Unorm:
+        case builtin::Function::kPack4X8Unorm:
             return "packUnorm4x8";
-        case sem::BuiltinType::kReverseBits:
+        case builtin::Function::kReverseBits:
             return "bitfieldReverse";
-        case sem::BuiltinType::kSmoothstep:
+        case builtin::Function::kSmoothstep:
             return "smoothstep";
-        case sem::BuiltinType::kUnpack2X16Float:
+        case builtin::Function::kUnpack2X16Float:
             return "unpackHalf2x16";
-        case sem::BuiltinType::kUnpack2X16Snorm:
+        case builtin::Function::kUnpack2X16Snorm:
             return "unpackSnorm2x16";
-        case sem::BuiltinType::kUnpack2X16Unorm:
+        case builtin::Function::kUnpack2X16Unorm:
             return "unpackUnorm2x16";
-        case sem::BuiltinType::kUnpack4X8Snorm:
+        case builtin::Function::kUnpack4X8Snorm:
             return "unpackSnorm4x8";
-        case sem::BuiltinType::kUnpack4X8Unorm:
+        case builtin::Function::kUnpack4X8Unorm:
             return "unpackUnorm4x8";
         default:
             diagnostics_.add_error(diag::System::Writer,
@@ -2367,7 +2389,7 @@
             return true;
         },
         [&](const type::I32*) {
-            out << constant->ValueAs<AInt>();
+            PrintI32(out, constant->ValueAs<i32>());
             return true;
         },
         [&](const type::U32*) {
@@ -2382,8 +2404,8 @@
 
             ScopedParen sp(out);
 
-            if (constant->AllEqual()) {
-                return EmitConstant(out, constant->Index(0));
+            if (auto* splat = constant->As<constant::Splat>()) {
+                return EmitConstant(out, splat->el);
             }
 
             for (size_t i = 0; i < v->Width(); i++) {
@@ -2483,12 +2505,20 @@
             }
             return true;
         },
-        [&](const ast::IntLiteralExpression* l) {
-            out << l->value;
-            if (l->suffix == ast::IntLiteralExpression::Suffix::kU) {
-                out << "u";
+        [&](const ast::IntLiteralExpression* i) {
+            switch (i->suffix) {
+                case ast::IntLiteralExpression::Suffix::kNone:
+                case ast::IntLiteralExpression::Suffix::kI: {
+                    PrintI32(out, static_cast<int32_t>(i->value));
+                    return true;
+                }
+                case ast::IntLiteralExpression::Suffix::kU: {
+                    out << i->value << "u";
+                    return true;
+                }
             }
-            return true;
+            diagnostics_.add_error(diag::System::Writer, "unknown integer literal suffix type");
+            return false;
         },
         [&](Default) {
             diagnostics_.add_error(diag::System::Writer, "unknown literal type");
@@ -3209,7 +3239,7 @@
         TextBuffer b;
         TINT_DEFER(helpers_.Append(b));
 
-        auto fn_name = UniqueIdentifier(std::string("tint_") + sem::str(builtin->Type()));
+        auto fn_name = UniqueIdentifier(std::string("tint_") + builtin::str(builtin->Type()));
         std::vector<std::string> parameter_names;
         {
             auto decl = line(&b);
diff --git a/src/tint/writer/glsl/generator_impl.h b/src/tint/writer/glsl/generator_impl.h
index d954921..96042c1 100644
--- a/src/tint/writer/glsl/generator_impl.h
+++ b/src/tint/writer/glsl/generator_impl.h
@@ -234,8 +234,11 @@
     /// Handles generating a call to the `countOneBits()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
+    /// @param builtin the semantic information for the builtin
     /// @returns true if the call expression is emitted
-    bool EmitSelectCall(utils::StringStream& out, const ast::CallExpression* expr);
+    bool EmitSelectCall(utils::StringStream& out,
+                        const ast::CallExpression* expr,
+                        const sem::Builtin* builtin);
     /// Handles generating a call to the `dot()` builtin
     /// @param out the output of the expression stream
     /// @param expr the call expression
diff --git a/src/tint/writer/glsl/generator_impl_builtin_test.cc b/src/tint/writer/glsl/generator_impl_builtin_test.cc
index d281cdb..27cd717 100644
--- a/src/tint/writer/glsl/generator_impl_builtin_test.cc
+++ b/src/tint/writer/glsl/generator_impl_builtin_test.cc
@@ -26,8 +26,6 @@
 namespace tint::writer::glsl {
 namespace {
 
-using BuiltinType = sem::BuiltinType;
-
 using GlslGeneratorImplTest_Builtin = TestHelper;
 
 enum class CallParamType {
@@ -38,7 +36,7 @@
 };
 
 struct BuiltinData {
-    BuiltinType builtin;
+    builtin::Function builtin;
     CallParamType type;
     const char* glsl_name;
 };
@@ -62,86 +60,86 @@
     return out;
 }
 
-const ast::CallExpression* GenerateCall(BuiltinType builtin,
+const ast::CallExpression* GenerateCall(builtin::Function builtin,
                                         CallParamType type,
                                         ProgramBuilder* builder) {
     std::string name;
     utils::StringStream str;
     str << name << builtin;
     switch (builtin) {
-        case BuiltinType::kAcos:
-        case BuiltinType::kAsin:
-        case BuiltinType::kAtan:
-        case BuiltinType::kCeil:
-        case BuiltinType::kCos:
-        case BuiltinType::kCosh:
-        case BuiltinType::kDpdx:
-        case BuiltinType::kDpdxCoarse:
-        case BuiltinType::kDpdxFine:
-        case BuiltinType::kDpdy:
-        case BuiltinType::kDpdyCoarse:
-        case BuiltinType::kDpdyFine:
-        case BuiltinType::kExp:
-        case BuiltinType::kExp2:
-        case BuiltinType::kFloor:
-        case BuiltinType::kFract:
-        case BuiltinType::kFwidth:
-        case BuiltinType::kFwidthCoarse:
-        case BuiltinType::kFwidthFine:
-        case BuiltinType::kInverseSqrt:
-        case BuiltinType::kLength:
-        case BuiltinType::kLog:
-        case BuiltinType::kLog2:
-        case BuiltinType::kNormalize:
-        case BuiltinType::kRound:
-        case BuiltinType::kSin:
-        case BuiltinType::kSinh:
-        case BuiltinType::kSqrt:
-        case BuiltinType::kTan:
-        case BuiltinType::kTanh:
-        case BuiltinType::kTrunc:
-        case BuiltinType::kSign:
+        case builtin::Function::kAcos:
+        case builtin::Function::kAsin:
+        case builtin::Function::kAtan:
+        case builtin::Function::kCeil:
+        case builtin::Function::kCos:
+        case builtin::Function::kCosh:
+        case builtin::Function::kDpdx:
+        case builtin::Function::kDpdxCoarse:
+        case builtin::Function::kDpdxFine:
+        case builtin::Function::kDpdy:
+        case builtin::Function::kDpdyCoarse:
+        case builtin::Function::kDpdyFine:
+        case builtin::Function::kExp:
+        case builtin::Function::kExp2:
+        case builtin::Function::kFloor:
+        case builtin::Function::kFract:
+        case builtin::Function::kFwidth:
+        case builtin::Function::kFwidthCoarse:
+        case builtin::Function::kFwidthFine:
+        case builtin::Function::kInverseSqrt:
+        case builtin::Function::kLength:
+        case builtin::Function::kLog:
+        case builtin::Function::kLog2:
+        case builtin::Function::kNormalize:
+        case builtin::Function::kRound:
+        case builtin::Function::kSin:
+        case builtin::Function::kSinh:
+        case builtin::Function::kSqrt:
+        case builtin::Function::kTan:
+        case builtin::Function::kTanh:
+        case builtin::Function::kTrunc:
+        case builtin::Function::kSign:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2");
             } else {
                 return builder->Call(str.str(), "f2");
             }
-        case BuiltinType::kLdexp:
+        case builtin::Function::kLdexp:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "i2");
             } else {
                 return builder->Call(str.str(), "f2", "i2");
             }
-        case BuiltinType::kAtan2:
-        case BuiltinType::kDot:
-        case BuiltinType::kDistance:
-        case BuiltinType::kPow:
-        case BuiltinType::kReflect:
-        case BuiltinType::kStep:
+        case builtin::Function::kAtan2:
+        case builtin::Function::kDot:
+        case builtin::Function::kDistance:
+        case builtin::Function::kPow:
+        case builtin::Function::kReflect:
+        case builtin::Function::kStep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2");
             }
-        case BuiltinType::kCross:
+        case builtin::Function::kCross:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h3", "h3");
             } else {
                 return builder->Call(str.str(), "f3", "f3");
             }
-        case BuiltinType::kFma:
-        case BuiltinType::kMix:
-        case BuiltinType::kFaceForward:
-        case BuiltinType::kSmoothstep:
+        case builtin::Function::kFma:
+        case builtin::Function::kMix:
+        case builtin::Function::kFaceForward:
+        case builtin::Function::kSmoothstep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             }
-        case BuiltinType::kAll:
-        case BuiltinType::kAny:
+        case builtin::Function::kAll:
+        case builtin::Function::kAny:
             return builder->Call(str.str(), "b2");
-        case BuiltinType::kAbs:
+        case builtin::Function::kAbs:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2");
             } else if (type == CallParamType::kF16) {
@@ -149,11 +147,11 @@
             } else {
                 return builder->Call(str.str(), "u2");
             }
-        case BuiltinType::kCountOneBits:
-        case BuiltinType::kReverseBits:
+        case builtin::Function::kCountOneBits:
+        case builtin::Function::kReverseBits:
             return builder->Call(str.str(), "u2");
-        case BuiltinType::kMax:
-        case BuiltinType::kMin:
+        case builtin::Function::kMax:
+        case builtin::Function::kMin:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -161,7 +159,7 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2");
             }
-        case BuiltinType::kClamp:
+        case builtin::Function::kClamp:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -169,19 +167,19 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2", "u2");
             }
-        case BuiltinType::kSelect:
+        case builtin::Function::kSelect:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "b2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "b2");
             }
-        case BuiltinType::kDeterminant:
+        case builtin::Function::kDeterminant:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm2x2");
             } else {
                 return builder->Call(str.str(), "m2x2");
             }
-        case BuiltinType::kTranspose:
+        case builtin::Function::kTranspose:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm3x2");
             } else {
@@ -235,110 +233,111 @@
 INSTANTIATE_TEST_SUITE_P(
     GlslGeneratorImplTest_Builtin,
     GlslBuiltinTest,
-    testing::Values(/* Logical built-in */
-                    BuiltinData{BuiltinType::kAll, CallParamType::kBool, "all"},
-                    BuiltinData{BuiltinType::kAny, CallParamType::kBool, "any"},
-                    /* Float built-in */
-                    BuiltinData{BuiltinType::kAbs, CallParamType::kF32, "abs"},
-                    BuiltinData{BuiltinType::kAbs, CallParamType::kF16, "abs"},
-                    BuiltinData{BuiltinType::kAcos, CallParamType::kF32, "acos"},
-                    BuiltinData{BuiltinType::kAcos, CallParamType::kF16, "acos"},
-                    BuiltinData{BuiltinType::kAsin, CallParamType::kF32, "asin"},
-                    BuiltinData{BuiltinType::kAsin, CallParamType::kF16, "asin"},
-                    BuiltinData{BuiltinType::kAtan, CallParamType::kF32, "atan"},
-                    BuiltinData{BuiltinType::kAtan, CallParamType::kF16, "atan"},
-                    BuiltinData{BuiltinType::kAtan2, CallParamType::kF32, "atan"},
-                    BuiltinData{BuiltinType::kAtan2, CallParamType::kF16, "atan"},
-                    BuiltinData{BuiltinType::kCeil, CallParamType::kF32, "ceil"},
-                    BuiltinData{BuiltinType::kCeil, CallParamType::kF16, "ceil"},
-                    BuiltinData{BuiltinType::kClamp, CallParamType::kF32, "clamp"},
-                    BuiltinData{BuiltinType::kClamp, CallParamType::kF16, "clamp"},
-                    BuiltinData{BuiltinType::kCos, CallParamType::kF32, "cos"},
-                    BuiltinData{BuiltinType::kCos, CallParamType::kF16, "cos"},
-                    BuiltinData{BuiltinType::kCosh, CallParamType::kF32, "cosh"},
-                    BuiltinData{BuiltinType::kCosh, CallParamType::kF16, "cosh"},
-                    BuiltinData{BuiltinType::kCross, CallParamType::kF32, "cross"},
-                    BuiltinData{BuiltinType::kCross, CallParamType::kF16, "cross"},
-                    BuiltinData{BuiltinType::kDistance, CallParamType::kF32, "distance"},
-                    BuiltinData{BuiltinType::kDistance, CallParamType::kF16, "distance"},
-                    BuiltinData{BuiltinType::kExp, CallParamType::kF32, "exp"},
-                    BuiltinData{BuiltinType::kExp, CallParamType::kF16, "exp"},
-                    BuiltinData{BuiltinType::kExp2, CallParamType::kF32, "exp2"},
-                    BuiltinData{BuiltinType::kExp2, CallParamType::kF16, "exp2"},
-                    BuiltinData{BuiltinType::kFaceForward, CallParamType::kF32, "faceforward"},
-                    BuiltinData{BuiltinType::kFaceForward, CallParamType::kF16, "faceforward"},
-                    BuiltinData{BuiltinType::kFloor, CallParamType::kF32, "floor"},
-                    BuiltinData{BuiltinType::kFloor, CallParamType::kF16, "floor"},
-                    BuiltinData{BuiltinType::kFma, CallParamType::kF32, "fma"},
-                    BuiltinData{BuiltinType::kFma, CallParamType::kF16, "fma"},
-                    BuiltinData{BuiltinType::kFract, CallParamType::kF32, "fract"},
-                    BuiltinData{BuiltinType::kFract, CallParamType::kF16, "fract"},
-                    BuiltinData{BuiltinType::kInverseSqrt, CallParamType::kF32, "inversesqrt"},
-                    BuiltinData{BuiltinType::kInverseSqrt, CallParamType::kF16, "inversesqrt"},
-                    BuiltinData{BuiltinType::kLdexp, CallParamType::kF32, "ldexp"},
-                    BuiltinData{BuiltinType::kLdexp, CallParamType::kF16, "ldexp"},
-                    BuiltinData{BuiltinType::kLength, CallParamType::kF32, "length"},
-                    BuiltinData{BuiltinType::kLength, CallParamType::kF16, "length"},
-                    BuiltinData{BuiltinType::kLog, CallParamType::kF32, "log"},
-                    BuiltinData{BuiltinType::kLog, CallParamType::kF16, "log"},
-                    BuiltinData{BuiltinType::kLog2, CallParamType::kF32, "log2"},
-                    BuiltinData{BuiltinType::kLog2, CallParamType::kF16, "log2"},
-                    BuiltinData{BuiltinType::kMax, CallParamType::kF32, "max"},
-                    BuiltinData{BuiltinType::kMax, CallParamType::kF16, "max"},
-                    BuiltinData{BuiltinType::kMin, CallParamType::kF32, "min"},
-                    BuiltinData{BuiltinType::kMin, CallParamType::kF16, "min"},
-                    BuiltinData{BuiltinType::kMix, CallParamType::kF32, "mix"},
-                    BuiltinData{BuiltinType::kMix, CallParamType::kF16, "mix"},
-                    BuiltinData{BuiltinType::kNormalize, CallParamType::kF32, "normalize"},
-                    BuiltinData{BuiltinType::kNormalize, CallParamType::kF16, "normalize"},
-                    BuiltinData{BuiltinType::kPow, CallParamType::kF32, "pow"},
-                    BuiltinData{BuiltinType::kPow, CallParamType::kF16, "pow"},
-                    BuiltinData{BuiltinType::kReflect, CallParamType::kF32, "reflect"},
-                    BuiltinData{BuiltinType::kReflect, CallParamType::kF16, "reflect"},
-                    BuiltinData{BuiltinType::kSign, CallParamType::kF32, "sign"},
-                    BuiltinData{BuiltinType::kSign, CallParamType::kF16, "sign"},
-                    BuiltinData{BuiltinType::kSin, CallParamType::kF32, "sin"},
-                    BuiltinData{BuiltinType::kSin, CallParamType::kF16, "sin"},
-                    BuiltinData{BuiltinType::kSinh, CallParamType::kF32, "sinh"},
-                    BuiltinData{BuiltinType::kSinh, CallParamType::kF16, "sinh"},
-                    BuiltinData{BuiltinType::kSmoothstep, CallParamType::kF32, "smoothstep"},
-                    BuiltinData{BuiltinType::kSmoothstep, CallParamType::kF16, "smoothstep"},
-                    BuiltinData{BuiltinType::kSqrt, CallParamType::kF32, "sqrt"},
-                    BuiltinData{BuiltinType::kSqrt, CallParamType::kF16, "sqrt"},
-                    BuiltinData{BuiltinType::kStep, CallParamType::kF32, "step"},
-                    BuiltinData{BuiltinType::kStep, CallParamType::kF16, "step"},
-                    BuiltinData{BuiltinType::kTan, CallParamType::kF32, "tan"},
-                    BuiltinData{BuiltinType::kTan, CallParamType::kF16, "tan"},
-                    BuiltinData{BuiltinType::kTanh, CallParamType::kF32, "tanh"},
-                    BuiltinData{BuiltinType::kTanh, CallParamType::kF16, "tanh"},
-                    BuiltinData{BuiltinType::kTrunc, CallParamType::kF32, "trunc"},
-                    BuiltinData{BuiltinType::kTrunc, CallParamType::kF16, "trunc"},
-                    /* Integer built-in */
-                    BuiltinData{BuiltinType::kAbs, CallParamType::kU32, "abs"},
-                    BuiltinData{BuiltinType::kClamp, CallParamType::kU32, "clamp"},
-                    BuiltinData{BuiltinType::kCountOneBits, CallParamType::kU32, "bitCount"},
-                    BuiltinData{BuiltinType::kMax, CallParamType::kU32, "max"},
-                    BuiltinData{BuiltinType::kMin, CallParamType::kU32, "min"},
-                    BuiltinData{BuiltinType::kReverseBits, CallParamType::kU32, "bitfieldReverse"},
-                    BuiltinData{BuiltinType::kRound, CallParamType::kU32, "round"},
-                    /* Matrix built-in */
-                    BuiltinData{BuiltinType::kDeterminant, CallParamType::kF32, "determinant"},
-                    BuiltinData{BuiltinType::kDeterminant, CallParamType::kF16, "determinant"},
-                    BuiltinData{BuiltinType::kTranspose, CallParamType::kF32, "transpose"},
-                    BuiltinData{BuiltinType::kTranspose, CallParamType::kF16, "transpose"},
-                    /* Vector built-in */
-                    BuiltinData{BuiltinType::kDot, CallParamType::kF32, "dot"},
-                    BuiltinData{BuiltinType::kDot, CallParamType::kF16, "dot"},
-                    /* Derivate built-in */
-                    BuiltinData{BuiltinType::kDpdx, CallParamType::kF32, "dFdx"},
-                    BuiltinData{BuiltinType::kDpdxCoarse, CallParamType::kF32, "dFdx"},
-                    BuiltinData{BuiltinType::kDpdxFine, CallParamType::kF32, "dFdx"},
-                    BuiltinData{BuiltinType::kDpdy, CallParamType::kF32, "dFdy"},
-                    BuiltinData{BuiltinType::kDpdyCoarse, CallParamType::kF32, "dFdy"},
-                    BuiltinData{BuiltinType::kDpdyFine, CallParamType::kF32, "dFdy"},
-                    BuiltinData{BuiltinType::kFwidth, CallParamType::kF32, "fwidth"},
-                    BuiltinData{BuiltinType::kFwidthCoarse, CallParamType::kF32, "fwidth"},
-                    BuiltinData{BuiltinType::kFwidthFine, CallParamType::kF32, "fwidth"}));
+    testing::
+        Values(/* Logical built-in */
+               BuiltinData{builtin::Function::kAll, CallParamType::kBool, "all"},
+               BuiltinData{builtin::Function::kAny, CallParamType::kBool, "any"},
+               /* Float built-in */
+               BuiltinData{builtin::Function::kAbs, CallParamType::kF32, "abs"},
+               BuiltinData{builtin::Function::kAbs, CallParamType::kF16, "abs"},
+               BuiltinData{builtin::Function::kAcos, CallParamType::kF32, "acos"},
+               BuiltinData{builtin::Function::kAcos, CallParamType::kF16, "acos"},
+               BuiltinData{builtin::Function::kAsin, CallParamType::kF32, "asin"},
+               BuiltinData{builtin::Function::kAsin, CallParamType::kF16, "asin"},
+               BuiltinData{builtin::Function::kAtan, CallParamType::kF32, "atan"},
+               BuiltinData{builtin::Function::kAtan, CallParamType::kF16, "atan"},
+               BuiltinData{builtin::Function::kAtan2, CallParamType::kF32, "atan"},
+               BuiltinData{builtin::Function::kAtan2, CallParamType::kF16, "atan"},
+               BuiltinData{builtin::Function::kCeil, CallParamType::kF32, "ceil"},
+               BuiltinData{builtin::Function::kCeil, CallParamType::kF16, "ceil"},
+               BuiltinData{builtin::Function::kClamp, CallParamType::kF32, "clamp"},
+               BuiltinData{builtin::Function::kClamp, CallParamType::kF16, "clamp"},
+               BuiltinData{builtin::Function::kCos, CallParamType::kF32, "cos"},
+               BuiltinData{builtin::Function::kCos, CallParamType::kF16, "cos"},
+               BuiltinData{builtin::Function::kCosh, CallParamType::kF32, "cosh"},
+               BuiltinData{builtin::Function::kCosh, CallParamType::kF16, "cosh"},
+               BuiltinData{builtin::Function::kCross, CallParamType::kF32, "cross"},
+               BuiltinData{builtin::Function::kCross, CallParamType::kF16, "cross"},
+               BuiltinData{builtin::Function::kDistance, CallParamType::kF32, "distance"},
+               BuiltinData{builtin::Function::kDistance, CallParamType::kF16, "distance"},
+               BuiltinData{builtin::Function::kExp, CallParamType::kF32, "exp"},
+               BuiltinData{builtin::Function::kExp, CallParamType::kF16, "exp"},
+               BuiltinData{builtin::Function::kExp2, CallParamType::kF32, "exp2"},
+               BuiltinData{builtin::Function::kExp2, CallParamType::kF16, "exp2"},
+               BuiltinData{builtin::Function::kFaceForward, CallParamType::kF32, "faceforward"},
+               BuiltinData{builtin::Function::kFaceForward, CallParamType::kF16, "faceforward"},
+               BuiltinData{builtin::Function::kFloor, CallParamType::kF32, "floor"},
+               BuiltinData{builtin::Function::kFloor, CallParamType::kF16, "floor"},
+               BuiltinData{builtin::Function::kFma, CallParamType::kF32, "fma"},
+               BuiltinData{builtin::Function::kFma, CallParamType::kF16, "fma"},
+               BuiltinData{builtin::Function::kFract, CallParamType::kF32, "fract"},
+               BuiltinData{builtin::Function::kFract, CallParamType::kF16, "fract"},
+               BuiltinData{builtin::Function::kInverseSqrt, CallParamType::kF32, "inversesqrt"},
+               BuiltinData{builtin::Function::kInverseSqrt, CallParamType::kF16, "inversesqrt"},
+               BuiltinData{builtin::Function::kLdexp, CallParamType::kF32, "ldexp"},
+               BuiltinData{builtin::Function::kLdexp, CallParamType::kF16, "ldexp"},
+               BuiltinData{builtin::Function::kLength, CallParamType::kF32, "length"},
+               BuiltinData{builtin::Function::kLength, CallParamType::kF16, "length"},
+               BuiltinData{builtin::Function::kLog, CallParamType::kF32, "log"},
+               BuiltinData{builtin::Function::kLog, CallParamType::kF16, "log"},
+               BuiltinData{builtin::Function::kLog2, CallParamType::kF32, "log2"},
+               BuiltinData{builtin::Function::kLog2, CallParamType::kF16, "log2"},
+               BuiltinData{builtin::Function::kMax, CallParamType::kF32, "max"},
+               BuiltinData{builtin::Function::kMax, CallParamType::kF16, "max"},
+               BuiltinData{builtin::Function::kMin, CallParamType::kF32, "min"},
+               BuiltinData{builtin::Function::kMin, CallParamType::kF16, "min"},
+               BuiltinData{builtin::Function::kMix, CallParamType::kF32, "mix"},
+               BuiltinData{builtin::Function::kMix, CallParamType::kF16, "mix"},
+               BuiltinData{builtin::Function::kNormalize, CallParamType::kF32, "normalize"},
+               BuiltinData{builtin::Function::kNormalize, CallParamType::kF16, "normalize"},
+               BuiltinData{builtin::Function::kPow, CallParamType::kF32, "pow"},
+               BuiltinData{builtin::Function::kPow, CallParamType::kF16, "pow"},
+               BuiltinData{builtin::Function::kReflect, CallParamType::kF32, "reflect"},
+               BuiltinData{builtin::Function::kReflect, CallParamType::kF16, "reflect"},
+               BuiltinData{builtin::Function::kSign, CallParamType::kF32, "sign"},
+               BuiltinData{builtin::Function::kSign, CallParamType::kF16, "sign"},
+               BuiltinData{builtin::Function::kSin, CallParamType::kF32, "sin"},
+               BuiltinData{builtin::Function::kSin, CallParamType::kF16, "sin"},
+               BuiltinData{builtin::Function::kSinh, CallParamType::kF32, "sinh"},
+               BuiltinData{builtin::Function::kSinh, CallParamType::kF16, "sinh"},
+               BuiltinData{builtin::Function::kSmoothstep, CallParamType::kF32, "smoothstep"},
+               BuiltinData{builtin::Function::kSmoothstep, CallParamType::kF16, "smoothstep"},
+               BuiltinData{builtin::Function::kSqrt, CallParamType::kF32, "sqrt"},
+               BuiltinData{builtin::Function::kSqrt, CallParamType::kF16, "sqrt"},
+               BuiltinData{builtin::Function::kStep, CallParamType::kF32, "step"},
+               BuiltinData{builtin::Function::kStep, CallParamType::kF16, "step"},
+               BuiltinData{builtin::Function::kTan, CallParamType::kF32, "tan"},
+               BuiltinData{builtin::Function::kTan, CallParamType::kF16, "tan"},
+               BuiltinData{builtin::Function::kTanh, CallParamType::kF32, "tanh"},
+               BuiltinData{builtin::Function::kTanh, CallParamType::kF16, "tanh"},
+               BuiltinData{builtin::Function::kTrunc, CallParamType::kF32, "trunc"},
+               BuiltinData{builtin::Function::kTrunc, CallParamType::kF16, "trunc"},
+               /* Integer built-in */
+               BuiltinData{builtin::Function::kAbs, CallParamType::kU32, "abs"},
+               BuiltinData{builtin::Function::kClamp, CallParamType::kU32, "clamp"},
+               BuiltinData{builtin::Function::kCountOneBits, CallParamType::kU32, "bitCount"},
+               BuiltinData{builtin::Function::kMax, CallParamType::kU32, "max"},
+               BuiltinData{builtin::Function::kMin, CallParamType::kU32, "min"},
+               BuiltinData{builtin::Function::kReverseBits, CallParamType::kU32, "bitfieldReverse"},
+               BuiltinData{builtin::Function::kRound, CallParamType::kU32, "round"},
+               /* Matrix built-in */
+               BuiltinData{builtin::Function::kDeterminant, CallParamType::kF32, "determinant"},
+               BuiltinData{builtin::Function::kDeterminant, CallParamType::kF16, "determinant"},
+               BuiltinData{builtin::Function::kTranspose, CallParamType::kF32, "transpose"},
+               BuiltinData{builtin::Function::kTranspose, CallParamType::kF16, "transpose"},
+               /* Vector built-in */
+               BuiltinData{builtin::Function::kDot, CallParamType::kF32, "dot"},
+               BuiltinData{builtin::Function::kDot, CallParamType::kF16, "dot"},
+               /* Derivate built-in */
+               BuiltinData{builtin::Function::kDpdx, CallParamType::kF32, "dFdx"},
+               BuiltinData{builtin::Function::kDpdxCoarse, CallParamType::kF32, "dFdx"},
+               BuiltinData{builtin::Function::kDpdxFine, CallParamType::kF32, "dFdx"},
+               BuiltinData{builtin::Function::kDpdy, CallParamType::kF32, "dFdy"},
+               BuiltinData{builtin::Function::kDpdyCoarse, CallParamType::kF32, "dFdy"},
+               BuiltinData{builtin::Function::kDpdyFine, CallParamType::kF32, "dFdy"},
+               BuiltinData{builtin::Function::kFwidth, CallParamType::kF32, "fwidth"},
+               BuiltinData{builtin::Function::kFwidthCoarse, CallParamType::kF32, "fwidth"},
+               BuiltinData{builtin::Function::kFwidthFine, CallParamType::kF32, "fwidth"}));
 
 TEST_F(GlslGeneratorImplTest_Builtin, Builtin_Call) {
     auto* call = Call("dot", "param1", "param2");
@@ -379,7 +378,7 @@
     gen.increment_indent();
     utils::StringStream out;
     ASSERT_TRUE(gen.EmitExpression(out, call)) << gen.error();
-    EXPECT_EQ(out.str(), "mix(a, b, bvec2(true, false))");
+    EXPECT_EQ(out.str(), "tint_select(a, b, bvec2(true, false))");
 }
 
 TEST_F(GlslGeneratorImplTest_Builtin, FMA_f32) {
diff --git a/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc b/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
index bbb3848..040adfe 100644
--- a/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
+++ b/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
@@ -98,6 +98,8 @@
             return R"(textureGather(Texture_Sampler, vec4(1.0f, 2.0f, 3.0f, float(4u)), 5.0f))";
         case ValidTextureOverload::kNumLayers2dArray:
         case ValidTextureOverload::kNumLayersDepth2dArray:
+        case ValidTextureOverload::kNumLayersCubeArray:
+        case ValidTextureOverload::kNumLayersDepthCubeArray:
             return {"textureSize"};
         case ValidTextureOverload::kNumLayersStorageWO2dArray:
             return {"imageSize"};
diff --git a/src/tint/writer/glsl/generator_impl_function_test.cc b/src/tint/writer/glsl/generator_impl_function_test.cc
index 05d1460..bc085b9 100644
--- a/src/tint/writer/glsl/generator_impl_function_test.cc
+++ b/src/tint/writer/glsl/generator_impl_function_test.cc
@@ -98,7 +98,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 void func() {
   return;
@@ -145,7 +145,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 layout(location = 0) in float foo_1;
 layout(location = 1) out float value;
@@ -186,7 +186,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 float frag_main(vec4 coord) {
   return coord.x;
@@ -239,7 +239,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 layout(location = 1) out float col1_1;
 layout(location = 2) out float col2_1;
@@ -378,7 +378,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct UBO {
   vec4 coord;
@@ -419,7 +419,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Uniforms {
   vec4 coord;
@@ -460,7 +460,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Data {
   int a;
@@ -508,7 +508,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(),
               R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Data {
   int a;
@@ -553,7 +553,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Data {
   int a;
@@ -598,7 +598,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Data {
   int a;
@@ -645,7 +645,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct S {
   float x;
@@ -692,7 +692,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(),
               R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct S {
   float x;
@@ -728,7 +728,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 void tint_symbol() {
 }
diff --git a/src/tint/writer/glsl/generator_impl_member_accessor_test.cc b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
index 2eb1b8e..6d0d58e 100644
--- a/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
@@ -281,7 +281,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Data {
   int a;
@@ -334,7 +334,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Data {
   float z;
@@ -382,7 +382,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Data {
   float z;
@@ -430,7 +430,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Data {
   float z;
@@ -477,7 +477,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Data {
   float z;
@@ -530,7 +530,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Inner {
   vec3 a;
@@ -591,7 +591,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Inner {
   vec3 a;
@@ -653,7 +653,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Inner {
   vec3 a;
@@ -714,7 +714,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Inner {
   vec3 a;
@@ -774,7 +774,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Inner {
   vec3 a;
@@ -835,7 +835,7 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     auto* expected =
         R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct Inner {
   ivec3 a;
diff --git a/src/tint/writer/glsl/generator_impl_sanitizer_test.cc b/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
index e85d050..5ed2be7 100644
--- a/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
+++ b/src/tint/writer/glsl/generator_impl_sanitizer_test.cc
@@ -43,7 +43,7 @@
 
     auto got = gen.result();
     auto* expect = R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 layout(binding = 1, std430) buffer my_struct_ssbo {
   float a[];
@@ -83,7 +83,7 @@
 
     auto got = gen.result();
     auto* expect = R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 layout(binding = 1, std430) buffer my_struct_ssbo {
   float z;
@@ -127,7 +127,7 @@
 
     auto got = gen.result();
     auto* expect = R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 layout(binding = 1, std430) buffer my_struct_ssbo {
   float a[];
@@ -164,7 +164,7 @@
 
     auto got = gen.result();
     auto* expect = R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 void tint_symbol() {
   int idx = 3;
@@ -206,7 +206,7 @@
 
     auto got = gen.result();
     auto* expect = R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 struct S {
   int a;
@@ -252,7 +252,7 @@
 
     auto got = gen.result();
     auto* expect = R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 void tint_symbol() {
   int v = 0;
@@ -301,7 +301,7 @@
 
     auto got = gen.result();
     auto* expect = R"(#version 310 es
-precision mediump float;
+precision highp float;
 
 void tint_symbol() {
   mat4 a[4] = mat4[4](mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), mat4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
diff --git a/src/tint/writer/glsl/generator_impl_switch_test.cc b/src/tint/writer/glsl/generator_impl_switch_test.cc
index b9ff49d..8e2f739 100644
--- a/src/tint/writer/glsl/generator_impl_switch_test.cc
+++ b/src/tint/writer/glsl/generator_impl_switch_test.cc
@@ -31,7 +31,7 @@
     auto* case_stmt = create<ast::CaseStatement>(utils::Vector{CaseSelector(5_i)}, case_body);
 
     auto* cond = Expr("cond");
-    auto* s = create<ast::SwitchStatement>(cond, utils::Vector{case_stmt, def});
+    auto* s = create<ast::SwitchStatement>(cond, utils::Vector{case_stmt, def}, utils::Empty);
     WrapInFunction(s);
 
     GeneratorImpl& gen = Build();
@@ -58,7 +58,7 @@
                                            def_body);
 
     auto* cond = Expr("cond");
-    auto* s = create<ast::SwitchStatement>(cond, utils::Vector{def});
+    auto* s = create<ast::SwitchStatement>(cond, utils::Vector{def}, utils::Empty);
     WrapInFunction(s);
 
     GeneratorImpl& gen = Build();
diff --git a/src/tint/writer/glsl/generator_impl_unary_op_test.cc b/src/tint/writer/glsl/generator_impl_unary_op_test.cc
index b7c5fd1..27e9d2c 100644
--- a/src/tint/writer/glsl/generator_impl_unary_op_test.cc
+++ b/src/tint/writer/glsl/generator_impl_unary_op_test.cc
@@ -80,5 +80,17 @@
     ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
     EXPECT_EQ(out.str(), "-(expr)");
 }
+
+TEST_F(GlslUnaryOpTest, IntMin) {
+    auto* op = Expr(i32(std::numeric_limits<int32_t>::min()));
+    WrapInFunction(op);
+
+    GeneratorImpl& gen = Build();
+
+    utils::StringStream out;
+    ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
+    EXPECT_EQ(out.str(), "(-2147483647 - 1)");
+}
+
 }  // namespace
 }  // namespace tint::writer::glsl
diff --git a/src/tint/writer/hlsl/generator.h b/src/tint/writer/hlsl/generator.h
index 74d79ba..2cdfe6f 100644
--- a/src/tint/writer/hlsl/generator.h
+++ b/src/tint/writer/hlsl/generator.h
@@ -28,6 +28,7 @@
 #include "src/tint/sem/binding_point.h"
 #include "src/tint/utils/bitset.h"
 #include "src/tint/writer/array_length_from_uniform_options.h"
+#include "src/tint/writer/external_texture_options.h"
 #include "src/tint/writer/text.h"
 
 // Forward declarations
@@ -58,8 +59,8 @@
     /// Set to `true` to disable workgroup memory zero initialization
     bool disable_workgroup_init = false;
 
-    /// Set to 'true' to generates binding mappings for external textures
-    bool generate_external_texture_bindings = false;
+    /// Options used in the binding mappings for external textures
+    ExternalTextureOptions external_texture_options = {};
 
     /// Options used to specify a mapping of binding points to indices into a UBO
     /// from which to load buffer sizes.
@@ -76,7 +77,7 @@
     TINT_REFLECT(disable_robustness,
                  root_constant_binding_point,
                  disable_workgroup_init,
-                 generate_external_texture_bindings,
+                 external_texture_options,
                  array_length_from_uniform);
 };
 
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index f0247c4..1cfbf64 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -40,6 +40,7 @@
 #include "src/tint/sem/value_constructor.h"
 #include "src/tint/sem/value_conversion.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/transform/add_empty_entry_point.h"
 #include "src/tint/transform/array_length_from_uniform.h"
 #include "src/tint/transform/builtin_polyfill.h"
@@ -52,6 +53,7 @@
 #include "src/tint/transform/expand_compound_assignment.h"
 #include "src/tint/transform/localize_struct_array_assignment.h"
 #include "src/tint/transform/manager.h"
+#include "src/tint/transform/multiplanar_external_texture.h"
 #include "src/tint/transform/num_workgroups_from_uniform.h"
 #include "src/tint/transform/promote_initializers_to_let.h"
 #include "src/tint/transform/promote_side_effects_to_decl.h"
@@ -80,7 +82,6 @@
 #include "src/tint/writer/append_vector.h"
 #include "src/tint/writer/check_supported_extensions.h"
 #include "src/tint/writer/float_to_string.h"
-#include "src/tint/writer/generate_external_texture_bindings.h"
 
 using namespace tint::number_suffixes;  // NOLINT
 
@@ -187,10 +188,10 @@
         manager.Add<transform::Robustness>();
     }
 
-    if (options.generate_external_texture_bindings) {
+    if (!options.external_texture_options.bindings_map.empty()) {
         // Note: it is more efficient for MultiplanarExternalTexture to come after Robustness
-        auto new_bindings_map = GenerateExternalTextureBindings(in);
-        data.Add<transform::MultiplanarExternalTexture::NewBindingPoints>(new_bindings_map);
+        data.Add<transform::MultiplanarExternalTexture::NewBindingPoints>(
+            options.external_texture_options.bindings_map);
         manager.Add<transform::MultiplanarExternalTexture>();
     }
 
@@ -203,6 +204,7 @@
         polyfills.clamp_int = true;
         // TODO(crbug.com/tint/1449): Some of these can map to HLSL's `firstbitlow`
         // and `firstbithigh`.
+        polyfills.conv_f32_to_iu32 = true;
         polyfills.count_leading_zeros = true;
         polyfills.count_trailing_zeros = true;
         polyfills.extract_bits = transform::BuiltinPolyfill::Level::kFull;
@@ -966,25 +968,25 @@
     if (builtin->IsTexture()) {
         return EmitTextureCall(out, call, builtin);
     }
-    if (type == sem::BuiltinType::kSelect) {
+    if (type == builtin::Function::kSelect) {
         return EmitSelectCall(out, expr);
     }
-    if (type == sem::BuiltinType::kModf) {
+    if (type == builtin::Function::kModf) {
         return EmitModfCall(out, expr, builtin);
     }
-    if (type == sem::BuiltinType::kFrexp) {
+    if (type == builtin::Function::kFrexp) {
         return EmitFrexpCall(out, expr, builtin);
     }
-    if (type == sem::BuiltinType::kDegrees) {
+    if (type == builtin::Function::kDegrees) {
         return EmitDegreesCall(out, expr, builtin);
     }
-    if (type == sem::BuiltinType::kRadians) {
+    if (type == builtin::Function::kRadians) {
         return EmitRadiansCall(out, expr, builtin);
     }
-    if (type == sem::BuiltinType::kSign) {
+    if (type == builtin::Function::kSign) {
         return EmitSignCall(out, call, builtin);
     }
-    if (type == sem::BuiltinType::kQuantizeToF16) {
+    if (type == builtin::Function::kQuantizeToF16) {
         return EmitQuantizeToF16Call(out, expr, builtin);
     }
     if (builtin->IsDataPacking()) {
@@ -1011,7 +1013,7 @@
     // Handle single argument builtins that only accept and return uint (not int overload). We need
     // to explicitly cast the return value (we also cast the arg for good measure). See
     // crbug.com/tint/1550
-    if (type == sem::BuiltinType::kCountOneBits || type == sem::BuiltinType::kReverseBits) {
+    if (type == builtin::Function::kCountOneBits || type == builtin::Function::kReverseBits) {
         auto* arg = call->Arguments()[0];
         if (arg->Type()->UnwrapRef()->is_signed_integer_scalar_or_vector()) {
             out << "asint(" << name << "(asuint(";
@@ -1805,7 +1807,7 @@
                 if (i > 0) {
                     pre << ", ";
                 }
-                if (i == 1 && builtin->Type() == sem::BuiltinType::kAtomicSub) {
+                if (i == 1 && builtin->Type() == builtin::Function::kAtomicSub) {
                     // Sub uses InterlockedAdd with the operand negated.
                     pre << "-";
                 }
@@ -1824,7 +1826,7 @@
     };
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kAtomicLoad: {
+        case builtin::Function::kAtomicLoad: {
             // HLSL does not have an InterlockedLoad, so we emulate it with
             // InterlockedOr using 0 as the OR value
             auto pre = line();
@@ -1841,7 +1843,7 @@
             out << result;
             return true;
         }
-        case sem::BuiltinType::kAtomicStore: {
+        case builtin::Function::kAtomicStore: {
             // HLSL does not have an InterlockedStore, so we emulate it with
             // InterlockedExchange and discard the returned value
             {  // T result = 0;
@@ -1872,7 +1874,7 @@
             }
             return true;
         }
-        case sem::BuiltinType::kAtomicCompareExchangeWeak: {
+        case builtin::Function::kAtomicCompareExchangeWeak: {
             if (!EmitStructType(&helpers_, builtin->ReturnType()->As<sem::Struct>())) {
                 return false;
             }
@@ -1921,26 +1923,26 @@
             return true;
         }
 
-        case sem::BuiltinType::kAtomicAdd:
-        case sem::BuiltinType::kAtomicSub:
+        case builtin::Function::kAtomicAdd:
+        case builtin::Function::kAtomicSub:
             return call("InterlockedAdd");
 
-        case sem::BuiltinType::kAtomicMax:
+        case builtin::Function::kAtomicMax:
             return call("InterlockedMax");
 
-        case sem::BuiltinType::kAtomicMin:
+        case builtin::Function::kAtomicMin:
             return call("InterlockedMin");
 
-        case sem::BuiltinType::kAtomicAnd:
+        case builtin::Function::kAtomicAnd:
             return call("InterlockedAnd");
 
-        case sem::BuiltinType::kAtomicOr:
+        case builtin::Function::kAtomicOr:
             return call("InterlockedOr");
 
-        case sem::BuiltinType::kAtomicXor:
+        case builtin::Function::kAtomicXor:
             return call("InterlockedXor");
 
-        case sem::BuiltinType::kAtomicExchange:
+        case builtin::Function::kAtomicExchange:
             return call("InterlockedExchange");
 
         default:
@@ -2114,21 +2116,21 @@
             uint32_t dims = 2;
             bool is_signed = false;
             uint32_t scale = 65535;
-            if (builtin->Type() == sem::BuiltinType::kPack4X8Snorm ||
-                builtin->Type() == sem::BuiltinType::kPack4X8Unorm) {
+            if (builtin->Type() == builtin::Function::kPack4X8Snorm ||
+                builtin->Type() == builtin::Function::kPack4X8Unorm) {
                 dims = 4;
                 scale = 255;
             }
-            if (builtin->Type() == sem::BuiltinType::kPack4X8Snorm ||
-                builtin->Type() == sem::BuiltinType::kPack2X16Snorm) {
+            if (builtin->Type() == builtin::Function::kPack4X8Snorm ||
+                builtin->Type() == builtin::Function::kPack2X16Snorm) {
                 is_signed = true;
                 scale = (scale - 1) / 2;
             }
             switch (builtin->Type()) {
-                case sem::BuiltinType::kPack4X8Snorm:
-                case sem::BuiltinType::kPack4X8Unorm:
-                case sem::BuiltinType::kPack2X16Snorm:
-                case sem::BuiltinType::kPack2X16Unorm: {
+                case builtin::Function::kPack4X8Snorm:
+                case builtin::Function::kPack4X8Unorm:
+                case builtin::Function::kPack2X16Snorm:
+                case builtin::Function::kPack2X16Unorm: {
                     {
                         auto l = line(b);
                         l << (is_signed ? "" : "u") << "int" << dims
@@ -2154,7 +2156,7 @@
                     }
                     break;
                 }
-                case sem::BuiltinType::kPack2X16Float: {
+                case builtin::Function::kPack2X16Float: {
                     line(b) << "uint2 i = f32tof16(" << params[0] << ");";
                     line(b) << "return i.x | (i.y << 16);";
                     break;
@@ -2177,19 +2179,19 @@
             uint32_t dims = 2;
             bool is_signed = false;
             uint32_t scale = 65535;
-            if (builtin->Type() == sem::BuiltinType::kUnpack4X8Snorm ||
-                builtin->Type() == sem::BuiltinType::kUnpack4X8Unorm) {
+            if (builtin->Type() == builtin::Function::kUnpack4X8Snorm ||
+                builtin->Type() == builtin::Function::kUnpack4X8Unorm) {
                 dims = 4;
                 scale = 255;
             }
-            if (builtin->Type() == sem::BuiltinType::kUnpack4X8Snorm ||
-                builtin->Type() == sem::BuiltinType::kUnpack2X16Snorm) {
+            if (builtin->Type() == builtin::Function::kUnpack4X8Snorm ||
+                builtin->Type() == builtin::Function::kUnpack2X16Snorm) {
                 is_signed = true;
                 scale = (scale - 1) / 2;
             }
             switch (builtin->Type()) {
-                case sem::BuiltinType::kUnpack4X8Snorm:
-                case sem::BuiltinType::kUnpack2X16Snorm: {
+                case builtin::Function::kUnpack4X8Snorm:
+                case builtin::Function::kUnpack2X16Snorm: {
                     line(b) << "int j = int(" << params[0] << ");";
                     {  // Perform sign extension on the converted values.
                         auto l = line(b);
@@ -2205,8 +2207,8 @@
                             << (is_signed ? "-1.0" : "0.0") << ", 1.0);";
                     break;
                 }
-                case sem::BuiltinType::kUnpack4X8Unorm:
-                case sem::BuiltinType::kUnpack2X16Unorm: {
+                case builtin::Function::kUnpack4X8Unorm:
+                case builtin::Function::kUnpack2X16Unorm: {
                     line(b) << "uint j = " << params[0] << ";";
                     {
                         auto l = line(b);
@@ -2222,7 +2224,7 @@
                     line(b) << "return float" << dims << "(i) / " << scale << ".0;";
                     break;
                 }
-                case sem::BuiltinType::kUnpack2X16Float:
+                case builtin::Function::kUnpack2X16Float:
                     line(b) << "uint i = " << params[0] << ";";
                     line(b) << "return f16tof32(uint2(i & 0xffff, i >> 16));";
                     break;
@@ -2244,11 +2246,11 @@
         out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
             std::string functionName;
             switch (builtin->Type()) {
-                case sem::BuiltinType::kDot4I8Packed:
+                case builtin::Function::kDot4I8Packed:
                     line(b) << "int accumulator = 0;";
                     functionName = "dot4add_i8packed";
                     break;
-                case sem::BuiltinType::kDot4U8Packed:
+                case builtin::Function::kDot4U8Packed:
                     line(b) << "uint accumulator = 0u;";
                     functionName = "dot4add_u8packed";
                     break;
@@ -2267,13 +2269,13 @@
 bool GeneratorImpl::EmitBarrierCall(utils::StringStream& out, const sem::Builtin* builtin) {
     // TODO(crbug.com/tint/661): Combine sequential barriers to a single
     // instruction.
-    if (builtin->Type() == sem::BuiltinType::kWorkgroupBarrier) {
+    if (builtin->Type() == builtin::Function::kWorkgroupBarrier) {
         out << "GroupMemoryBarrierWithGroupSync()";
-    } else if (builtin->Type() == sem::BuiltinType::kStorageBarrier) {
+    } else if (builtin->Type() == builtin::Function::kStorageBarrier) {
         out << "DeviceMemoryBarrierWithGroupSync()";
     } else {
         TINT_UNREACHABLE(Writer, diagnostics_)
-            << "unexpected barrier builtin type " << sem::str(builtin->Type());
+            << "unexpected barrier builtin type " << builtin::str(builtin->Type());
         return false;
     }
     return true;
@@ -2303,10 +2305,10 @@
     auto* texture_type = TypeOf(texture)->UnwrapRef()->As<type::Texture>();
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kTextureDimensions:
-        case sem::BuiltinType::kTextureNumLayers:
-        case sem::BuiltinType::kTextureNumLevels:
-        case sem::BuiltinType::kTextureNumSamples: {
+        case builtin::Function::kTextureDimensions:
+        case builtin::Function::kTextureNumLayers:
+        case builtin::Function::kTextureNumLevels:
+        case builtin::Function::kTextureNumSamples: {
             // All of these builtins use the GetDimensions() method on the texture
             bool is_ms =
                 texture_type->IsAnyOf<type::MultisampledTexture, type::DepthMultisampledTexture>();
@@ -2314,7 +2316,7 @@
             std::string swizzle;
 
             switch (builtin->Type()) {
-                case sem::BuiltinType::kTextureDimensions:
+                case builtin::Function::kTextureDimensions:
                     switch (texture_type->dim()) {
                         case type::TextureDimension::kNone:
                             TINT_ICE(Writer, diagnostics_) << "texture dimension is kNone";
@@ -2342,7 +2344,7 @@
                             break;
                     }
                     break;
-                case sem::BuiltinType::kTextureNumLayers:
+                case builtin::Function::kTextureNumLayers:
                     switch (texture_type->dim()) {
                         default:
                             TINT_ICE(Writer, diagnostics_) << "texture dimension is not arrayed";
@@ -2357,7 +2359,7 @@
                             break;
                     }
                     break;
-                case sem::BuiltinType::kTextureNumLevels:
+                case builtin::Function::kTextureNumLevels:
                     switch (texture_type->dim()) {
                         default:
                             TINT_ICE(Writer, diagnostics_)
@@ -2380,7 +2382,7 @@
                             break;
                     }
                     break;
-                case sem::BuiltinType::kTextureNumSamples:
+                case builtin::Function::kTextureNumSamples:
                     switch (texture_type->dim()) {
                         default:
                             TINT_ICE(Writer, diagnostics_)
@@ -2427,9 +2429,9 @@
             // Declare a variable to hold the queried texture info
             auto dims = UniqueIdentifier(kTempNamePrefix);
             if (num_dimensions == 1) {
-                line() << "int " << dims << ";";
+                line() << "uint " << dims << ";";
             } else {
-                line() << "int" << num_dimensions << " " << dims << ";";
+                line() << "uint" << num_dimensions << " " << dims << ";";
             }
 
             {  // texture.GetDimensions(...)
@@ -2444,7 +2446,7 @@
                         return false;
                     }
                     pre << ", ";
-                } else if (builtin->Type() == sem::BuiltinType::kTextureNumLevels) {
+                } else if (builtin->Type() == builtin::Function::kTextureNumLevels) {
                     pre << "0, ";
                 }
 
@@ -2491,34 +2493,34 @@
     uint32_t hlsl_ret_width = 4u;
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kTextureSample:
+        case builtin::Function::kTextureSample:
             out << ".Sample(";
             break;
-        case sem::BuiltinType::kTextureSampleBias:
+        case builtin::Function::kTextureSampleBias:
             out << ".SampleBias(";
             break;
-        case sem::BuiltinType::kTextureSampleLevel:
+        case builtin::Function::kTextureSampleLevel:
             out << ".SampleLevel(";
             break;
-        case sem::BuiltinType::kTextureSampleGrad:
+        case builtin::Function::kTextureSampleGrad:
             out << ".SampleGrad(";
             break;
-        case sem::BuiltinType::kTextureSampleCompare:
+        case builtin::Function::kTextureSampleCompare:
             out << ".SampleCmp(";
             hlsl_ret_width = 1;
             break;
-        case sem::BuiltinType::kTextureSampleCompareLevel:
+        case builtin::Function::kTextureSampleCompareLevel:
             out << ".SampleCmpLevelZero(";
             hlsl_ret_width = 1;
             break;
-        case sem::BuiltinType::kTextureLoad:
+        case builtin::Function::kTextureLoad:
             out << ".Load(";
             // Multisampled textures do not support mip-levels.
             if (!texture_type->Is<type::MultisampledTexture>()) {
                 pack_level_in_coords = true;
             }
             break;
-        case sem::BuiltinType::kTextureGather:
+        case builtin::Function::kTextureGather:
             out << ".Gather";
             if (builtin->Parameters()[0]->Usage() == sem::ParameterUsage::kComponent) {
                 switch (call->Arguments()[0]->ConstantValue()->ValueAs<AInt>()) {
@@ -2538,10 +2540,10 @@
             }
             out << "(";
             break;
-        case sem::BuiltinType::kTextureGatherCompare:
+        case builtin::Function::kTextureGatherCompare:
             out << ".GatherCmp(";
             break;
-        case sem::BuiltinType::kTextureStore:
+        case builtin::Function::kTextureStore:
             out << "[";
             break;
         default:
@@ -2621,7 +2623,7 @@
         }
     }
 
-    if (builtin->Type() == sem::BuiltinType::kTextureStore) {
+    if (builtin->Type() == builtin::Function::kTextureStore) {
         out << "] = ";
         if (!EmitExpression(out, arg(Usage::kValue))) {
             return false;
@@ -2655,78 +2657,78 @@
 
 std::string GeneratorImpl::generate_builtin_name(const sem::Builtin* builtin) {
     switch (builtin->Type()) {
-        case sem::BuiltinType::kAbs:
-        case sem::BuiltinType::kAcos:
-        case sem::BuiltinType::kAll:
-        case sem::BuiltinType::kAny:
-        case sem::BuiltinType::kAsin:
-        case sem::BuiltinType::kAtan:
-        case sem::BuiltinType::kAtan2:
-        case sem::BuiltinType::kCeil:
-        case sem::BuiltinType::kClamp:
-        case sem::BuiltinType::kCos:
-        case sem::BuiltinType::kCosh:
-        case sem::BuiltinType::kCross:
-        case sem::BuiltinType::kDeterminant:
-        case sem::BuiltinType::kDistance:
-        case sem::BuiltinType::kDot:
-        case sem::BuiltinType::kExp:
-        case sem::BuiltinType::kExp2:
-        case sem::BuiltinType::kFloor:
-        case sem::BuiltinType::kFrexp:
-        case sem::BuiltinType::kLdexp:
-        case sem::BuiltinType::kLength:
-        case sem::BuiltinType::kLog:
-        case sem::BuiltinType::kLog2:
-        case sem::BuiltinType::kMax:
-        case sem::BuiltinType::kMin:
-        case sem::BuiltinType::kModf:
-        case sem::BuiltinType::kNormalize:
-        case sem::BuiltinType::kPow:
-        case sem::BuiltinType::kReflect:
-        case sem::BuiltinType::kRefract:
-        case sem::BuiltinType::kRound:
-        case sem::BuiltinType::kSaturate:
-        case sem::BuiltinType::kSin:
-        case sem::BuiltinType::kSinh:
-        case sem::BuiltinType::kSqrt:
-        case sem::BuiltinType::kStep:
-        case sem::BuiltinType::kTan:
-        case sem::BuiltinType::kTanh:
-        case sem::BuiltinType::kTranspose:
-        case sem::BuiltinType::kTrunc:
+        case builtin::Function::kAbs:
+        case builtin::Function::kAcos:
+        case builtin::Function::kAll:
+        case builtin::Function::kAny:
+        case builtin::Function::kAsin:
+        case builtin::Function::kAtan:
+        case builtin::Function::kAtan2:
+        case builtin::Function::kCeil:
+        case builtin::Function::kClamp:
+        case builtin::Function::kCos:
+        case builtin::Function::kCosh:
+        case builtin::Function::kCross:
+        case builtin::Function::kDeterminant:
+        case builtin::Function::kDistance:
+        case builtin::Function::kDot:
+        case builtin::Function::kExp:
+        case builtin::Function::kExp2:
+        case builtin::Function::kFloor:
+        case builtin::Function::kFrexp:
+        case builtin::Function::kLdexp:
+        case builtin::Function::kLength:
+        case builtin::Function::kLog:
+        case builtin::Function::kLog2:
+        case builtin::Function::kMax:
+        case builtin::Function::kMin:
+        case builtin::Function::kModf:
+        case builtin::Function::kNormalize:
+        case builtin::Function::kPow:
+        case builtin::Function::kReflect:
+        case builtin::Function::kRefract:
+        case builtin::Function::kRound:
+        case builtin::Function::kSaturate:
+        case builtin::Function::kSin:
+        case builtin::Function::kSinh:
+        case builtin::Function::kSqrt:
+        case builtin::Function::kStep:
+        case builtin::Function::kTan:
+        case builtin::Function::kTanh:
+        case builtin::Function::kTranspose:
+        case builtin::Function::kTrunc:
             return builtin->str();
-        case sem::BuiltinType::kCountOneBits:  // uint
+        case builtin::Function::kCountOneBits:  // uint
             return "countbits";
-        case sem::BuiltinType::kDpdx:
+        case builtin::Function::kDpdx:
             return "ddx";
-        case sem::BuiltinType::kDpdxCoarse:
+        case builtin::Function::kDpdxCoarse:
             return "ddx_coarse";
-        case sem::BuiltinType::kDpdxFine:
+        case builtin::Function::kDpdxFine:
             return "ddx_fine";
-        case sem::BuiltinType::kDpdy:
+        case builtin::Function::kDpdy:
             return "ddy";
-        case sem::BuiltinType::kDpdyCoarse:
+        case builtin::Function::kDpdyCoarse:
             return "ddy_coarse";
-        case sem::BuiltinType::kDpdyFine:
+        case builtin::Function::kDpdyFine:
             return "ddy_fine";
-        case sem::BuiltinType::kFaceForward:
+        case builtin::Function::kFaceForward:
             return "faceforward";
-        case sem::BuiltinType::kFract:
+        case builtin::Function::kFract:
             return "frac";
-        case sem::BuiltinType::kFma:
+        case builtin::Function::kFma:
             return "mad";
-        case sem::BuiltinType::kFwidth:
-        case sem::BuiltinType::kFwidthCoarse:
-        case sem::BuiltinType::kFwidthFine:
+        case builtin::Function::kFwidth:
+        case builtin::Function::kFwidthCoarse:
+        case builtin::Function::kFwidthFine:
             return "fwidth";
-        case sem::BuiltinType::kInverseSqrt:
+        case builtin::Function::kInverseSqrt:
             return "rsqrt";
-        case sem::BuiltinType::kMix:
+        case builtin::Function::kMix:
             return "lerp";
-        case sem::BuiltinType::kReverseBits:  // uint
+        case builtin::Function::kReverseBits:  // uint
             return "reversebits";
-        case sem::BuiltinType::kSmoothstep:
+        case builtin::Function::kSmoothstep:
             return "smoothstep";
         default:
             diagnostics_.add_error(diag::System::Writer,
@@ -3321,10 +3323,10 @@
             return true;
         },
         [&](const type::Vector* v) {
-            if (constant->AllEqual()) {
+            if (auto* splat = constant->As<constant::Splat>()) {
                 {
                     ScopedParen sp(out);
-                    if (!EmitConstant(out, constant->Index(0), is_variable_initializer)) {
+                    if (!EmitConstant(out, splat->el, is_variable_initializer)) {
                         return false;
                     }
                 }
@@ -4346,7 +4348,7 @@
         TextBuffer b;
         TINT_DEFER(helpers_.Append(b));
 
-        auto fn_name = UniqueIdentifier(std::string("tint_") + sem::str(builtin->Type()));
+        auto fn_name = UniqueIdentifier(std::string("tint_") + builtin::str(builtin->Type()));
         std::vector<std::string> parameter_names;
         {
             auto decl = line(&b);
diff --git a/src/tint/writer/hlsl/generator_impl_builtin_test.cc b/src/tint/writer/hlsl/generator_impl_builtin_test.cc
index 5054327..f473ac8 100644
--- a/src/tint/writer/hlsl/generator_impl_builtin_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_builtin_test.cc
@@ -26,7 +26,6 @@
 namespace tint::writer::hlsl {
 namespace {
 
-using BuiltinType = sem::BuiltinType;
 using HlslGeneratorImplTest_Builtin = TestHelper;
 
 enum class CallParamType {
@@ -37,7 +36,7 @@
 };
 
 struct BuiltinData {
-    BuiltinType builtin;
+    builtin::Function builtin;
     CallParamType type;
     const char* hlsl_name;
 };
@@ -61,85 +60,85 @@
     return out;
 }
 
-const ast::CallExpression* GenerateCall(BuiltinType builtin,
+const ast::CallExpression* GenerateCall(builtin::Function builtin,
                                         CallParamType type,
                                         ProgramBuilder* builder) {
     std::string name;
     utils::StringStream str;
     str << name << builtin;
     switch (builtin) {
-        case BuiltinType::kAcos:
-        case BuiltinType::kAsin:
-        case BuiltinType::kAtan:
-        case BuiltinType::kCeil:
-        case BuiltinType::kCos:
-        case BuiltinType::kCosh:
-        case BuiltinType::kDpdx:
-        case BuiltinType::kDpdxCoarse:
-        case BuiltinType::kDpdxFine:
-        case BuiltinType::kDpdy:
-        case BuiltinType::kDpdyCoarse:
-        case BuiltinType::kDpdyFine:
-        case BuiltinType::kExp:
-        case BuiltinType::kExp2:
-        case BuiltinType::kFloor:
-        case BuiltinType::kFract:
-        case BuiltinType::kFwidth:
-        case BuiltinType::kFwidthCoarse:
-        case BuiltinType::kFwidthFine:
-        case BuiltinType::kInverseSqrt:
-        case BuiltinType::kLength:
-        case BuiltinType::kLog:
-        case BuiltinType::kLog2:
-        case BuiltinType::kNormalize:
-        case BuiltinType::kRound:
-        case BuiltinType::kSin:
-        case BuiltinType::kSinh:
-        case BuiltinType::kSqrt:
-        case BuiltinType::kTan:
-        case BuiltinType::kTanh:
-        case BuiltinType::kTrunc:
+        case builtin::Function::kAcos:
+        case builtin::Function::kAsin:
+        case builtin::Function::kAtan:
+        case builtin::Function::kCeil:
+        case builtin::Function::kCos:
+        case builtin::Function::kCosh:
+        case builtin::Function::kDpdx:
+        case builtin::Function::kDpdxCoarse:
+        case builtin::Function::kDpdxFine:
+        case builtin::Function::kDpdy:
+        case builtin::Function::kDpdyCoarse:
+        case builtin::Function::kDpdyFine:
+        case builtin::Function::kExp:
+        case builtin::Function::kExp2:
+        case builtin::Function::kFloor:
+        case builtin::Function::kFract:
+        case builtin::Function::kFwidth:
+        case builtin::Function::kFwidthCoarse:
+        case builtin::Function::kFwidthFine:
+        case builtin::Function::kInverseSqrt:
+        case builtin::Function::kLength:
+        case builtin::Function::kLog:
+        case builtin::Function::kLog2:
+        case builtin::Function::kNormalize:
+        case builtin::Function::kRound:
+        case builtin::Function::kSin:
+        case builtin::Function::kSinh:
+        case builtin::Function::kSqrt:
+        case builtin::Function::kTan:
+        case builtin::Function::kTanh:
+        case builtin::Function::kTrunc:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2");
             } else {
                 return builder->Call(str.str(), "f2");
             }
-        case BuiltinType::kLdexp:
+        case builtin::Function::kLdexp:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "i2");
             } else {
                 return builder->Call(str.str(), "f2", "i2");
             }
-        case BuiltinType::kAtan2:
-        case BuiltinType::kDot:
-        case BuiltinType::kDistance:
-        case BuiltinType::kPow:
-        case BuiltinType::kReflect:
-        case BuiltinType::kStep:
+        case builtin::Function::kAtan2:
+        case builtin::Function::kDot:
+        case builtin::Function::kDistance:
+        case builtin::Function::kPow:
+        case builtin::Function::kReflect:
+        case builtin::Function::kStep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2");
             }
-        case BuiltinType::kCross:
+        case builtin::Function::kCross:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h3", "h3");
             } else {
                 return builder->Call(str.str(), "f3", "f3");
             }
-        case BuiltinType::kFma:
-        case BuiltinType::kMix:
-        case BuiltinType::kFaceForward:
-        case BuiltinType::kSmoothstep:
+        case builtin::Function::kFma:
+        case builtin::Function::kMix:
+        case builtin::Function::kFaceForward:
+        case builtin::Function::kSmoothstep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             }
-        case BuiltinType::kAll:
-        case BuiltinType::kAny:
+        case builtin::Function::kAll:
+        case builtin::Function::kAny:
             return builder->Call(str.str(), "b2");
-        case BuiltinType::kAbs:
+        case builtin::Function::kAbs:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2");
             } else if (type == CallParamType::kF16) {
@@ -147,11 +146,11 @@
             } else {
                 return builder->Call(str.str(), "u2");
             }
-        case BuiltinType::kCountOneBits:
-        case BuiltinType::kReverseBits:
+        case builtin::Function::kCountOneBits:
+        case builtin::Function::kReverseBits:
             return builder->Call(str.str(), "u2");
-        case BuiltinType::kMax:
-        case BuiltinType::kMin:
+        case builtin::Function::kMax:
+        case builtin::Function::kMin:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -159,7 +158,7 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2");
             }
-        case BuiltinType::kClamp:
+        case builtin::Function::kClamp:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -167,19 +166,19 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2", "u2");
             }
-        case BuiltinType::kSelect:
+        case builtin::Function::kSelect:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "b2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "b2");
             }
-        case BuiltinType::kDeterminant:
+        case builtin::Function::kDeterminant:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm2x2");
             } else {
                 return builder->Call(str.str(), "m2x2");
             }
-        case BuiltinType::kTranspose:
+        case builtin::Function::kTranspose:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm3x2");
             } else {
@@ -235,107 +234,112 @@
     HlslGeneratorImplTest_Builtin,
     HlslBuiltinTest,
     testing::Values(/* Logical built-in */
-                    BuiltinData{BuiltinType::kAll, CallParamType::kBool, "all"},
-                    BuiltinData{BuiltinType::kAny, CallParamType::kBool, "any"},
+                    BuiltinData{builtin::Function::kAll, CallParamType::kBool, "all"},
+                    BuiltinData{builtin::Function::kAny, CallParamType::kBool, "any"},
                     /* Float built-in */
-                    BuiltinData{BuiltinType::kAbs, CallParamType::kF32, "abs"},
-                    BuiltinData{BuiltinType::kAbs, CallParamType::kF16, "abs"},
-                    BuiltinData{BuiltinType::kAcos, CallParamType::kF32, "acos"},
-                    BuiltinData{BuiltinType::kAcos, CallParamType::kF16, "acos"},
-                    BuiltinData{BuiltinType::kAsin, CallParamType::kF32, "asin"},
-                    BuiltinData{BuiltinType::kAsin, CallParamType::kF16, "asin"},
-                    BuiltinData{BuiltinType::kAtan, CallParamType::kF32, "atan"},
-                    BuiltinData{BuiltinType::kAtan, CallParamType::kF16, "atan"},
-                    BuiltinData{BuiltinType::kAtan2, CallParamType::kF32, "atan2"},
-                    BuiltinData{BuiltinType::kAtan2, CallParamType::kF16, "atan2"},
-                    BuiltinData{BuiltinType::kCeil, CallParamType::kF32, "ceil"},
-                    BuiltinData{BuiltinType::kCeil, CallParamType::kF16, "ceil"},
-                    BuiltinData{BuiltinType::kClamp, CallParamType::kF32, "clamp"},
-                    BuiltinData{BuiltinType::kClamp, CallParamType::kF16, "clamp"},
-                    BuiltinData{BuiltinType::kCos, CallParamType::kF32, "cos"},
-                    BuiltinData{BuiltinType::kCos, CallParamType::kF16, "cos"},
-                    BuiltinData{BuiltinType::kCosh, CallParamType::kF32, "cosh"},
-                    BuiltinData{BuiltinType::kCosh, CallParamType::kF16, "cosh"},
-                    BuiltinData{BuiltinType::kCross, CallParamType::kF32, "cross"},
-                    BuiltinData{BuiltinType::kCross, CallParamType::kF16, "cross"},
-                    BuiltinData{BuiltinType::kDistance, CallParamType::kF32, "distance"},
-                    BuiltinData{BuiltinType::kDistance, CallParamType::kF16, "distance"},
-                    BuiltinData{BuiltinType::kExp, CallParamType::kF32, "exp"},
-                    BuiltinData{BuiltinType::kExp, CallParamType::kF16, "exp"},
-                    BuiltinData{BuiltinType::kExp2, CallParamType::kF32, "exp2"},
-                    BuiltinData{BuiltinType::kExp2, CallParamType::kF16, "exp2"},
-                    BuiltinData{BuiltinType::kFaceForward, CallParamType::kF32, "faceforward"},
-                    BuiltinData{BuiltinType::kFaceForward, CallParamType::kF16, "faceforward"},
-                    BuiltinData{BuiltinType::kFloor, CallParamType::kF32, "floor"},
-                    BuiltinData{BuiltinType::kFloor, CallParamType::kF16, "floor"},
-                    BuiltinData{BuiltinType::kFma, CallParamType::kF32, "mad"},
-                    BuiltinData{BuiltinType::kFma, CallParamType::kF16, "mad"},
-                    BuiltinData{BuiltinType::kFract, CallParamType::kF32, "frac"},
-                    BuiltinData{BuiltinType::kFract, CallParamType::kF16, "frac"},
-                    BuiltinData{BuiltinType::kInverseSqrt, CallParamType::kF32, "rsqrt"},
-                    BuiltinData{BuiltinType::kInverseSqrt, CallParamType::kF16, "rsqrt"},
-                    BuiltinData{BuiltinType::kLdexp, CallParamType::kF32, "ldexp"},
-                    BuiltinData{BuiltinType::kLdexp, CallParamType::kF16, "ldexp"},
-                    BuiltinData{BuiltinType::kLength, CallParamType::kF32, "length"},
-                    BuiltinData{BuiltinType::kLength, CallParamType::kF16, "length"},
-                    BuiltinData{BuiltinType::kLog, CallParamType::kF32, "log"},
-                    BuiltinData{BuiltinType::kLog, CallParamType::kF16, "log"},
-                    BuiltinData{BuiltinType::kLog2, CallParamType::kF32, "log2"},
-                    BuiltinData{BuiltinType::kLog2, CallParamType::kF16, "log2"},
-                    BuiltinData{BuiltinType::kMax, CallParamType::kF32, "max"},
-                    BuiltinData{BuiltinType::kMax, CallParamType::kF16, "max"},
-                    BuiltinData{BuiltinType::kMin, CallParamType::kF32, "min"},
-                    BuiltinData{BuiltinType::kMin, CallParamType::kF16, "min"},
-                    BuiltinData{BuiltinType::kMix, CallParamType::kF32, "lerp"},
-                    BuiltinData{BuiltinType::kMix, CallParamType::kF16, "lerp"},
-                    BuiltinData{BuiltinType::kNormalize, CallParamType::kF32, "normalize"},
-                    BuiltinData{BuiltinType::kNormalize, CallParamType::kF16, "normalize"},
-                    BuiltinData{BuiltinType::kPow, CallParamType::kF32, "pow"},
-                    BuiltinData{BuiltinType::kPow, CallParamType::kF16, "pow"},
-                    BuiltinData{BuiltinType::kReflect, CallParamType::kF32, "reflect"},
-                    BuiltinData{BuiltinType::kReflect, CallParamType::kF16, "reflect"},
-                    BuiltinData{BuiltinType::kSin, CallParamType::kF32, "sin"},
-                    BuiltinData{BuiltinType::kSin, CallParamType::kF16, "sin"},
-                    BuiltinData{BuiltinType::kSinh, CallParamType::kF32, "sinh"},
-                    BuiltinData{BuiltinType::kSinh, CallParamType::kF16, "sinh"},
-                    BuiltinData{BuiltinType::kSmoothstep, CallParamType::kF32, "smoothstep"},
-                    BuiltinData{BuiltinType::kSmoothstep, CallParamType::kF16, "smoothstep"},
-                    BuiltinData{BuiltinType::kSqrt, CallParamType::kF32, "sqrt"},
-                    BuiltinData{BuiltinType::kSqrt, CallParamType::kF16, "sqrt"},
-                    BuiltinData{BuiltinType::kStep, CallParamType::kF32, "step"},
-                    BuiltinData{BuiltinType::kStep, CallParamType::kF16, "step"},
-                    BuiltinData{BuiltinType::kTan, CallParamType::kF32, "tan"},
-                    BuiltinData{BuiltinType::kTan, CallParamType::kF16, "tan"},
-                    BuiltinData{BuiltinType::kTanh, CallParamType::kF32, "tanh"},
-                    BuiltinData{BuiltinType::kTanh, CallParamType::kF16, "tanh"},
-                    BuiltinData{BuiltinType::kTrunc, CallParamType::kF32, "trunc"},
-                    BuiltinData{BuiltinType::kTrunc, CallParamType::kF16, "trunc"},
+                    BuiltinData{builtin::Function::kAbs, CallParamType::kF32, "abs"},
+                    BuiltinData{builtin::Function::kAbs, CallParamType::kF16, "abs"},
+                    BuiltinData{builtin::Function::kAcos, CallParamType::kF32, "acos"},
+                    BuiltinData{builtin::Function::kAcos, CallParamType::kF16, "acos"},
+                    BuiltinData{builtin::Function::kAsin, CallParamType::kF32, "asin"},
+                    BuiltinData{builtin::Function::kAsin, CallParamType::kF16, "asin"},
+                    BuiltinData{builtin::Function::kAtan, CallParamType::kF32, "atan"},
+                    BuiltinData{builtin::Function::kAtan, CallParamType::kF16, "atan"},
+                    BuiltinData{builtin::Function::kAtan2, CallParamType::kF32, "atan2"},
+                    BuiltinData{builtin::Function::kAtan2, CallParamType::kF16, "atan2"},
+                    BuiltinData{builtin::Function::kCeil, CallParamType::kF32, "ceil"},
+                    BuiltinData{builtin::Function::kCeil, CallParamType::kF16, "ceil"},
+                    BuiltinData{builtin::Function::kClamp, CallParamType::kF32, "clamp"},
+                    BuiltinData{builtin::Function::kClamp, CallParamType::kF16, "clamp"},
+                    BuiltinData{builtin::Function::kCos, CallParamType::kF32, "cos"},
+                    BuiltinData{builtin::Function::kCos, CallParamType::kF16, "cos"},
+                    BuiltinData{builtin::Function::kCosh, CallParamType::kF32, "cosh"},
+                    BuiltinData{builtin::Function::kCosh, CallParamType::kF16, "cosh"},
+                    BuiltinData{builtin::Function::kCross, CallParamType::kF32, "cross"},
+                    BuiltinData{builtin::Function::kCross, CallParamType::kF16, "cross"},
+                    BuiltinData{builtin::Function::kDistance, CallParamType::kF32, "distance"},
+                    BuiltinData{builtin::Function::kDistance, CallParamType::kF16, "distance"},
+                    BuiltinData{builtin::Function::kExp, CallParamType::kF32, "exp"},
+                    BuiltinData{builtin::Function::kExp, CallParamType::kF16, "exp"},
+                    BuiltinData{builtin::Function::kExp2, CallParamType::kF32, "exp2"},
+                    BuiltinData{builtin::Function::kExp2, CallParamType::kF16, "exp2"},
+                    BuiltinData{builtin::Function::kFaceForward, CallParamType::kF32,
+                                "faceforward"},
+                    BuiltinData{builtin::Function::kFaceForward, CallParamType::kF16,
+                                "faceforward"},
+                    BuiltinData{builtin::Function::kFloor, CallParamType::kF32, "floor"},
+                    BuiltinData{builtin::Function::kFloor, CallParamType::kF16, "floor"},
+                    BuiltinData{builtin::Function::kFma, CallParamType::kF32, "mad"},
+                    BuiltinData{builtin::Function::kFma, CallParamType::kF16, "mad"},
+                    BuiltinData{builtin::Function::kFract, CallParamType::kF32, "frac"},
+                    BuiltinData{builtin::Function::kFract, CallParamType::kF16, "frac"},
+                    BuiltinData{builtin::Function::kInverseSqrt, CallParamType::kF32, "rsqrt"},
+                    BuiltinData{builtin::Function::kInverseSqrt, CallParamType::kF16, "rsqrt"},
+                    BuiltinData{builtin::Function::kLdexp, CallParamType::kF32, "ldexp"},
+                    BuiltinData{builtin::Function::kLdexp, CallParamType::kF16, "ldexp"},
+                    BuiltinData{builtin::Function::kLength, CallParamType::kF32, "length"},
+                    BuiltinData{builtin::Function::kLength, CallParamType::kF16, "length"},
+                    BuiltinData{builtin::Function::kLog, CallParamType::kF32, "log"},
+                    BuiltinData{builtin::Function::kLog, CallParamType::kF16, "log"},
+                    BuiltinData{builtin::Function::kLog2, CallParamType::kF32, "log2"},
+                    BuiltinData{builtin::Function::kLog2, CallParamType::kF16, "log2"},
+                    BuiltinData{builtin::Function::kMax, CallParamType::kF32, "max"},
+                    BuiltinData{builtin::Function::kMax, CallParamType::kF16, "max"},
+                    BuiltinData{builtin::Function::kMin, CallParamType::kF32, "min"},
+                    BuiltinData{builtin::Function::kMin, CallParamType::kF16, "min"},
+                    BuiltinData{builtin::Function::kMix, CallParamType::kF32, "lerp"},
+                    BuiltinData{builtin::Function::kMix, CallParamType::kF16, "lerp"},
+                    BuiltinData{builtin::Function::kNormalize, CallParamType::kF32, "normalize"},
+                    BuiltinData{builtin::Function::kNormalize, CallParamType::kF16, "normalize"},
+                    BuiltinData{builtin::Function::kPow, CallParamType::kF32, "pow"},
+                    BuiltinData{builtin::Function::kPow, CallParamType::kF16, "pow"},
+                    BuiltinData{builtin::Function::kReflect, CallParamType::kF32, "reflect"},
+                    BuiltinData{builtin::Function::kReflect, CallParamType::kF16, "reflect"},
+                    BuiltinData{builtin::Function::kSin, CallParamType::kF32, "sin"},
+                    BuiltinData{builtin::Function::kSin, CallParamType::kF16, "sin"},
+                    BuiltinData{builtin::Function::kSinh, CallParamType::kF32, "sinh"},
+                    BuiltinData{builtin::Function::kSinh, CallParamType::kF16, "sinh"},
+                    BuiltinData{builtin::Function::kSmoothstep, CallParamType::kF32, "smoothstep"},
+                    BuiltinData{builtin::Function::kSmoothstep, CallParamType::kF16, "smoothstep"},
+                    BuiltinData{builtin::Function::kSqrt, CallParamType::kF32, "sqrt"},
+                    BuiltinData{builtin::Function::kSqrt, CallParamType::kF16, "sqrt"},
+                    BuiltinData{builtin::Function::kStep, CallParamType::kF32, "step"},
+                    BuiltinData{builtin::Function::kStep, CallParamType::kF16, "step"},
+                    BuiltinData{builtin::Function::kTan, CallParamType::kF32, "tan"},
+                    BuiltinData{builtin::Function::kTan, CallParamType::kF16, "tan"},
+                    BuiltinData{builtin::Function::kTanh, CallParamType::kF32, "tanh"},
+                    BuiltinData{builtin::Function::kTanh, CallParamType::kF16, "tanh"},
+                    BuiltinData{builtin::Function::kTrunc, CallParamType::kF32, "trunc"},
+                    BuiltinData{builtin::Function::kTrunc, CallParamType::kF16, "trunc"},
                     /* Integer built-in */
-                    BuiltinData{BuiltinType::kAbs, CallParamType::kU32, "abs"},
-                    BuiltinData{BuiltinType::kClamp, CallParamType::kU32, "clamp"},
-                    BuiltinData{BuiltinType::kCountOneBits, CallParamType::kU32, "countbits"},
-                    BuiltinData{BuiltinType::kMax, CallParamType::kU32, "max"},
-                    BuiltinData{BuiltinType::kMin, CallParamType::kU32, "min"},
-                    BuiltinData{BuiltinType::kReverseBits, CallParamType::kU32, "reversebits"},
-                    BuiltinData{BuiltinType::kRound, CallParamType::kU32, "round"},
+                    BuiltinData{builtin::Function::kAbs, CallParamType::kU32, "abs"},
+                    BuiltinData{builtin::Function::kClamp, CallParamType::kU32, "clamp"},
+                    BuiltinData{builtin::Function::kCountOneBits, CallParamType::kU32, "countbits"},
+                    BuiltinData{builtin::Function::kMax, CallParamType::kU32, "max"},
+                    BuiltinData{builtin::Function::kMin, CallParamType::kU32, "min"},
+                    BuiltinData{builtin::Function::kReverseBits, CallParamType::kU32,
+                                "reversebits"},
+                    BuiltinData{builtin::Function::kRound, CallParamType::kU32, "round"},
                     /* Matrix built-in */
-                    BuiltinData{BuiltinType::kDeterminant, CallParamType::kF32, "determinant"},
-                    BuiltinData{BuiltinType::kDeterminant, CallParamType::kF16, "determinant"},
-                    BuiltinData{BuiltinType::kTranspose, CallParamType::kF32, "transpose"},
-                    BuiltinData{BuiltinType::kTranspose, CallParamType::kF16, "transpose"},
+                    BuiltinData{builtin::Function::kDeterminant, CallParamType::kF32,
+                                "determinant"},
+                    BuiltinData{builtin::Function::kDeterminant, CallParamType::kF16,
+                                "determinant"},
+                    BuiltinData{builtin::Function::kTranspose, CallParamType::kF32, "transpose"},
+                    BuiltinData{builtin::Function::kTranspose, CallParamType::kF16, "transpose"},
                     /* Vector built-in */
-                    BuiltinData{BuiltinType::kDot, CallParamType::kF32, "dot"},
-                    BuiltinData{BuiltinType::kDot, CallParamType::kF16, "dot"},
+                    BuiltinData{builtin::Function::kDot, CallParamType::kF32, "dot"},
+                    BuiltinData{builtin::Function::kDot, CallParamType::kF16, "dot"},
                     /* Derivate built-in */
-                    BuiltinData{BuiltinType::kDpdx, CallParamType::kF32, "ddx"},
-                    BuiltinData{BuiltinType::kDpdxCoarse, CallParamType::kF32, "ddx_coarse"},
-                    BuiltinData{BuiltinType::kDpdxFine, CallParamType::kF32, "ddx_fine"},
-                    BuiltinData{BuiltinType::kDpdy, CallParamType::kF32, "ddy"},
-                    BuiltinData{BuiltinType::kDpdyCoarse, CallParamType::kF32, "ddy_coarse"},
-                    BuiltinData{BuiltinType::kDpdyFine, CallParamType::kF32, "ddy_fine"},
-                    BuiltinData{BuiltinType::kFwidth, CallParamType::kF32, "fwidth"},
-                    BuiltinData{BuiltinType::kFwidthCoarse, CallParamType::kF32, "fwidth"},
-                    BuiltinData{BuiltinType::kFwidthFine, CallParamType::kF32, "fwidth"}));
+                    BuiltinData{builtin::Function::kDpdx, CallParamType::kF32, "ddx"},
+                    BuiltinData{builtin::Function::kDpdxCoarse, CallParamType::kF32, "ddx_coarse"},
+                    BuiltinData{builtin::Function::kDpdxFine, CallParamType::kF32, "ddx_fine"},
+                    BuiltinData{builtin::Function::kDpdy, CallParamType::kF32, "ddy"},
+                    BuiltinData{builtin::Function::kDpdyCoarse, CallParamType::kF32, "ddy_coarse"},
+                    BuiltinData{builtin::Function::kDpdyFine, CallParamType::kF32, "ddy_fine"},
+                    BuiltinData{builtin::Function::kFwidth, CallParamType::kF32, "fwidth"},
+                    BuiltinData{builtin::Function::kFwidthCoarse, CallParamType::kF32, "fwidth"},
+                    BuiltinData{builtin::Function::kFwidthFine, CallParamType::kF32, "fwidth"}));
 
 TEST_F(HlslGeneratorImplTest_Builtin, Builtin_Call) {
     auto* call = Call("dot", "param1", "param2");
diff --git a/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc b/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc
index 6751790..4d7165b 100644
--- a/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_builtin_texture_test.cc
@@ -170,6 +170,8 @@
             return R"(tint_symbol.GatherCmp(tint_symbol_1, float4(1.0f, 2.0f, 3.0f, float(4u)), 5.0f))";
         case ValidTextureOverload::kNumLayers2dArray:
         case ValidTextureOverload::kNumLayersDepth2dArray:
+        case ValidTextureOverload::kNumLayersCubeArray:
+        case ValidTextureOverload::kNumLayersDepthCubeArray:
         case ValidTextureOverload::kNumLayersStorageWO2dArray:
             return {
                 R"(int3 tint_tmp;
diff --git a/src/tint/writer/msl/generator.h b/src/tint/writer/msl/generator.h
index 1add259..b4e11d9 100644
--- a/src/tint/writer/msl/generator.h
+++ b/src/tint/writer/msl/generator.h
@@ -23,6 +23,7 @@
 
 #include "src/tint/reflection.h"
 #include "src/tint/writer/array_length_from_uniform_options.h"
+#include "src/tint/writer/external_texture_options.h"
 #include "src/tint/writer/text.h"
 
 // Forward declarations
@@ -62,8 +63,8 @@
     /// Set to `true` to disable workgroup memory zero initialization
     bool disable_workgroup_init = false;
 
-    /// Set to 'true' to generates binding mappings for external textures
-    bool generate_external_texture_bindings = false;
+    /// Options used in the binding mappings for external textures
+    ExternalTextureOptions external_texture_options = {};
 
     /// Options used to specify a mapping of binding points to indices into a UBO
     /// from which to load buffer sizes.
@@ -75,7 +76,7 @@
                  fixed_sample_mask,
                  emit_vertex_point_size,
                  disable_workgroup_init,
-                 generate_external_texture_bindings,
+                 external_texture_options,
                  array_length_from_uniform);
 };
 
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 5c9e11b..268736f 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -40,6 +40,7 @@
 #include "src/tint/sem/value_constructor.h"
 #include "src/tint/sem/value_conversion.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/switch.h"
 #include "src/tint/transform/array_length_from_uniform.h"
 #include "src/tint/transform/builtin_polyfill.h"
 #include "src/tint/transform/canonicalize_entry_point_io.h"
@@ -48,6 +49,7 @@
 #include "src/tint/transform/expand_compound_assignment.h"
 #include "src/tint/transform/manager.h"
 #include "src/tint/transform/module_scope_var_to_entry_point_param.h"
+#include "src/tint/transform/multiplanar_external_texture.h"
 #include "src/tint/transform/packed_vec3.h"
 #include "src/tint/transform/preserve_padding.h"
 #include "src/tint/transform/promote_initializers_to_let.h"
@@ -82,7 +84,6 @@
 #include "src/tint/utils/string_stream.h"
 #include "src/tint/writer/check_supported_extensions.h"
 #include "src/tint/writer/float_to_string.h"
-#include "src/tint/writer/generate_external_texture_bindings.h"
 
 namespace tint::writer::msl {
 namespace {
@@ -218,6 +219,7 @@
         polyfills.atanh = transform::BuiltinPolyfill::Level::kRangeCheck;
         polyfills.bitshift_modulo = true;  // crbug.com/tint/1543
         polyfills.clamp_int = true;
+        polyfills.conv_f32_to_iu32 = true;
         polyfills.extract_bits = transform::BuiltinPolyfill::Level::kClampParameters;
         polyfills.first_leading_bit = true;
         polyfills.first_trailing_bit = true;
@@ -230,10 +232,10 @@
         manager.Add<transform::BuiltinPolyfill>();
     }
 
-    if (options.generate_external_texture_bindings) {
+    if (!options.external_texture_options.bindings_map.empty()) {
         // Note: it is more efficient for MultiplanarExternalTexture to come after Robustness
-        auto new_bindings_map = GenerateExternalTextureBindings(in);
-        data.Add<transform::MultiplanarExternalTexture::NewBindingPoints>(new_bindings_map);
+        data.Add<transform::MultiplanarExternalTexture::NewBindingPoints>(
+            options.external_texture_options.bindings_map);
         manager.Add<transform::MultiplanarExternalTexture>();
     }
 
@@ -699,20 +701,20 @@
     auto name = generate_builtin_name(builtin);
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kDot:
+        case builtin::Function::kDot:
             return EmitDotCall(out, expr, builtin);
-        case sem::BuiltinType::kModf:
+        case builtin::Function::kModf:
             return EmitModfCall(out, expr, builtin);
-        case sem::BuiltinType::kFrexp:
+        case builtin::Function::kFrexp:
             return EmitFrexpCall(out, expr, builtin);
-        case sem::BuiltinType::kDegrees:
+        case builtin::Function::kDegrees:
             return EmitDegreesCall(out, expr, builtin);
-        case sem::BuiltinType::kRadians:
+        case builtin::Function::kRadians:
             return EmitRadiansCall(out, expr, builtin);
 
-        case sem::BuiltinType::kPack2X16Float:
-        case sem::BuiltinType::kUnpack2X16Float: {
-            if (builtin->Type() == sem::BuiltinType::kPack2X16Float) {
+        case builtin::Function::kPack2X16Float:
+        case builtin::Function::kUnpack2X16Float: {
+            if (builtin->Type() == builtin::Function::kPack2X16Float) {
                 out << "as_type<uint>(half2(";
             } else {
                 out << "float2(as_type<half2>(";
@@ -723,7 +725,7 @@
             out << "))";
             return true;
         }
-        case sem::BuiltinType::kQuantizeToF16: {
+        case builtin::Function::kQuantizeToF16: {
             std::string width = "";
             if (auto* vec = builtin->ReturnType()->As<type::Vector>()) {
                 width = std::to_string(vec->Width());
@@ -737,16 +739,16 @@
         }
         // TODO(crbug.com/tint/661): Combine sequential barriers to a single
         // instruction.
-        case sem::BuiltinType::kStorageBarrier: {
+        case builtin::Function::kStorageBarrier: {
             out << "threadgroup_barrier(mem_flags::mem_device)";
             return true;
         }
-        case sem::BuiltinType::kWorkgroupBarrier: {
+        case builtin::Function::kWorkgroupBarrier: {
             out << "threadgroup_barrier(mem_flags::mem_threadgroup)";
             return true;
         }
 
-        case sem::BuiltinType::kLength: {
+        case builtin::Function::kLength: {
             auto* sem = builder_.Sem().GetVal(expr->args[0]);
             if (sem->Type()->UnwrapRef()->is_scalar()) {
                 // Emulate scalar overload using fabs(x).
@@ -755,7 +757,7 @@
             break;
         }
 
-        case sem::BuiltinType::kDistance: {
+        case builtin::Function::kDistance: {
             auto* sem = builder_.Sem().GetVal(expr->args[0]);
             if (sem->Type()->UnwrapRef()->is_scalar()) {
                 // Emulate scalar overload using fabs(x - y);
@@ -896,37 +898,37 @@
     };
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kAtomicLoad:
+        case builtin::Function::kAtomicLoad:
             return call("atomic_load_explicit", true);
 
-        case sem::BuiltinType::kAtomicStore:
+        case builtin::Function::kAtomicStore:
             return call("atomic_store_explicit", true);
 
-        case sem::BuiltinType::kAtomicAdd:
+        case builtin::Function::kAtomicAdd:
             return call("atomic_fetch_add_explicit", true);
 
-        case sem::BuiltinType::kAtomicSub:
+        case builtin::Function::kAtomicSub:
             return call("atomic_fetch_sub_explicit", true);
 
-        case sem::BuiltinType::kAtomicMax:
+        case builtin::Function::kAtomicMax:
             return call("atomic_fetch_max_explicit", true);
 
-        case sem::BuiltinType::kAtomicMin:
+        case builtin::Function::kAtomicMin:
             return call("atomic_fetch_min_explicit", true);
 
-        case sem::BuiltinType::kAtomicAnd:
+        case builtin::Function::kAtomicAnd:
             return call("atomic_fetch_and_explicit", true);
 
-        case sem::BuiltinType::kAtomicOr:
+        case builtin::Function::kAtomicOr:
             return call("atomic_fetch_or_explicit", true);
 
-        case sem::BuiltinType::kAtomicXor:
+        case builtin::Function::kAtomicXor:
             return call("atomic_fetch_xor_explicit", true);
 
-        case sem::BuiltinType::kAtomicExchange:
+        case builtin::Function::kAtomicExchange:
             return call("atomic_exchange_explicit", true);
 
-        case sem::BuiltinType::kAtomicCompareExchangeWeak: {
+        case builtin::Function::kAtomicCompareExchangeWeak: {
             auto* ptr_ty = TypeOf(expr->args[0])->UnwrapRef()->As<type::Pointer>();
             auto sc = ptr_ty->AddressSpace();
             auto* str = builtin->ReturnType()->As<sem::Struct>();
@@ -1041,7 +1043,7 @@
     bool level_is_constant_zero = texture_type->dim() == type::TextureDimension::k1d;
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kTextureDimensions: {
+        case builtin::Function::kTextureDimensions: {
             std::vector<const char*> dims;
             switch (texture_type->dim()) {
                 case type::TextureDimension::kNone:
@@ -1094,21 +1096,21 @@
             }
             return true;
         }
-        case sem::BuiltinType::kTextureNumLayers: {
+        case builtin::Function::kTextureNumLayers: {
             if (!texture_expr()) {
                 return false;
             }
             out << ".get_array_size()";
             return true;
         }
-        case sem::BuiltinType::kTextureNumLevels: {
+        case builtin::Function::kTextureNumLevels: {
             if (!texture_expr()) {
                 return false;
             }
             out << ".get_num_mip_levels()";
             return true;
         }
-        case sem::BuiltinType::kTextureNumSamples: {
+        case builtin::Function::kTextureNumSamples: {
             if (!texture_expr()) {
                 return false;
             }
@@ -1126,27 +1128,27 @@
     bool lod_param_is_named = true;
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kTextureSample:
-        case sem::BuiltinType::kTextureSampleBias:
-        case sem::BuiltinType::kTextureSampleLevel:
-        case sem::BuiltinType::kTextureSampleGrad:
+        case builtin::Function::kTextureSample:
+        case builtin::Function::kTextureSampleBias:
+        case builtin::Function::kTextureSampleLevel:
+        case builtin::Function::kTextureSampleGrad:
             out << ".sample(";
             break;
-        case sem::BuiltinType::kTextureSampleCompare:
-        case sem::BuiltinType::kTextureSampleCompareLevel:
+        case builtin::Function::kTextureSampleCompare:
+        case builtin::Function::kTextureSampleCompareLevel:
             out << ".sample_compare(";
             break;
-        case sem::BuiltinType::kTextureGather:
+        case builtin::Function::kTextureGather:
             out << ".gather(";
             break;
-        case sem::BuiltinType::kTextureGatherCompare:
+        case builtin::Function::kTextureGatherCompare:
             out << ".gather_compare(";
             break;
-        case sem::BuiltinType::kTextureLoad:
+        case builtin::Function::kTextureLoad:
             out << ".read(";
             lod_param_is_named = false;
             break;
-        case sem::BuiltinType::kTextureStore:
+        case builtin::Function::kTextureStore:
             out << ".write(";
             break;
         default:
@@ -1223,7 +1225,7 @@
             out << ")";
         }
     }
-    if (builtin->Type() == sem::BuiltinType::kTextureSampleCompareLevel) {
+    if (builtin->Type() == builtin::Function::kTextureSampleCompareLevel) {
         maybe_write_comma();
         out << "level(0)";
     }
@@ -1430,143 +1432,143 @@
 std::string GeneratorImpl::generate_builtin_name(const sem::Builtin* builtin) {
     std::string out = "";
     switch (builtin->Type()) {
-        case sem::BuiltinType::kAcos:
-        case sem::BuiltinType::kAcosh:
-        case sem::BuiltinType::kAll:
-        case sem::BuiltinType::kAny:
-        case sem::BuiltinType::kAsin:
-        case sem::BuiltinType::kAsinh:
-        case sem::BuiltinType::kAtanh:
-        case sem::BuiltinType::kAtan:
-        case sem::BuiltinType::kAtan2:
-        case sem::BuiltinType::kCeil:
-        case sem::BuiltinType::kCos:
-        case sem::BuiltinType::kCosh:
-        case sem::BuiltinType::kCross:
-        case sem::BuiltinType::kDeterminant:
-        case sem::BuiltinType::kDistance:
-        case sem::BuiltinType::kDot:
-        case sem::BuiltinType::kExp:
-        case sem::BuiltinType::kExp2:
-        case sem::BuiltinType::kFloor:
-        case sem::BuiltinType::kFma:
-        case sem::BuiltinType::kFract:
-        case sem::BuiltinType::kFrexp:
-        case sem::BuiltinType::kLength:
-        case sem::BuiltinType::kLdexp:
-        case sem::BuiltinType::kLog:
-        case sem::BuiltinType::kLog2:
-        case sem::BuiltinType::kMix:
-        case sem::BuiltinType::kModf:
-        case sem::BuiltinType::kNormalize:
-        case sem::BuiltinType::kPow:
-        case sem::BuiltinType::kReflect:
-        case sem::BuiltinType::kRefract:
-        case sem::BuiltinType::kSaturate:
-        case sem::BuiltinType::kSelect:
-        case sem::BuiltinType::kSin:
-        case sem::BuiltinType::kSinh:
-        case sem::BuiltinType::kSqrt:
-        case sem::BuiltinType::kStep:
-        case sem::BuiltinType::kTan:
-        case sem::BuiltinType::kTanh:
-        case sem::BuiltinType::kTranspose:
-        case sem::BuiltinType::kTrunc:
-        case sem::BuiltinType::kSign:
-        case sem::BuiltinType::kClamp:
+        case builtin::Function::kAcos:
+        case builtin::Function::kAcosh:
+        case builtin::Function::kAll:
+        case builtin::Function::kAny:
+        case builtin::Function::kAsin:
+        case builtin::Function::kAsinh:
+        case builtin::Function::kAtanh:
+        case builtin::Function::kAtan:
+        case builtin::Function::kAtan2:
+        case builtin::Function::kCeil:
+        case builtin::Function::kCos:
+        case builtin::Function::kCosh:
+        case builtin::Function::kCross:
+        case builtin::Function::kDeterminant:
+        case builtin::Function::kDistance:
+        case builtin::Function::kDot:
+        case builtin::Function::kExp:
+        case builtin::Function::kExp2:
+        case builtin::Function::kFloor:
+        case builtin::Function::kFma:
+        case builtin::Function::kFract:
+        case builtin::Function::kFrexp:
+        case builtin::Function::kLength:
+        case builtin::Function::kLdexp:
+        case builtin::Function::kLog:
+        case builtin::Function::kLog2:
+        case builtin::Function::kMix:
+        case builtin::Function::kModf:
+        case builtin::Function::kNormalize:
+        case builtin::Function::kPow:
+        case builtin::Function::kReflect:
+        case builtin::Function::kRefract:
+        case builtin::Function::kSaturate:
+        case builtin::Function::kSelect:
+        case builtin::Function::kSin:
+        case builtin::Function::kSinh:
+        case builtin::Function::kSqrt:
+        case builtin::Function::kStep:
+        case builtin::Function::kTan:
+        case builtin::Function::kTanh:
+        case builtin::Function::kTranspose:
+        case builtin::Function::kTrunc:
+        case builtin::Function::kSign:
+        case builtin::Function::kClamp:
             out += builtin->str();
             break;
-        case sem::BuiltinType::kAbs:
+        case builtin::Function::kAbs:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 out += "fabs";
             } else {
                 out += "abs";
             }
             break;
-        case sem::BuiltinType::kCountLeadingZeros:
+        case builtin::Function::kCountLeadingZeros:
             out += "clz";
             break;
-        case sem::BuiltinType::kCountOneBits:
+        case builtin::Function::kCountOneBits:
             out += "popcount";
             break;
-        case sem::BuiltinType::kCountTrailingZeros:
+        case builtin::Function::kCountTrailingZeros:
             out += "ctz";
             break;
-        case sem::BuiltinType::kDpdx:
-        case sem::BuiltinType::kDpdxCoarse:
-        case sem::BuiltinType::kDpdxFine:
+        case builtin::Function::kDpdx:
+        case builtin::Function::kDpdxCoarse:
+        case builtin::Function::kDpdxFine:
             out += "dfdx";
             break;
-        case sem::BuiltinType::kDpdy:
-        case sem::BuiltinType::kDpdyCoarse:
-        case sem::BuiltinType::kDpdyFine:
+        case builtin::Function::kDpdy:
+        case builtin::Function::kDpdyCoarse:
+        case builtin::Function::kDpdyFine:
             out += "dfdy";
             break;
-        case sem::BuiltinType::kExtractBits:
+        case builtin::Function::kExtractBits:
             out += "extract_bits";
             break;
-        case sem::BuiltinType::kInsertBits:
+        case builtin::Function::kInsertBits:
             out += "insert_bits";
             break;
-        case sem::BuiltinType::kFwidth:
-        case sem::BuiltinType::kFwidthCoarse:
-        case sem::BuiltinType::kFwidthFine:
+        case builtin::Function::kFwidth:
+        case builtin::Function::kFwidthCoarse:
+        case builtin::Function::kFwidthFine:
             out += "fwidth";
             break;
-        case sem::BuiltinType::kMax:
+        case builtin::Function::kMax:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 out += "fmax";
             } else {
                 out += "max";
             }
             break;
-        case sem::BuiltinType::kMin:
+        case builtin::Function::kMin:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 out += "fmin";
             } else {
                 out += "min";
             }
             break;
-        case sem::BuiltinType::kFaceForward:
+        case builtin::Function::kFaceForward:
             out += "faceforward";
             break;
-        case sem::BuiltinType::kPack4X8Snorm:
+        case builtin::Function::kPack4X8Snorm:
             out += "pack_float_to_snorm4x8";
             break;
-        case sem::BuiltinType::kPack4X8Unorm:
+        case builtin::Function::kPack4X8Unorm:
             out += "pack_float_to_unorm4x8";
             break;
-        case sem::BuiltinType::kPack2X16Snorm:
+        case builtin::Function::kPack2X16Snorm:
             out += "pack_float_to_snorm2x16";
             break;
-        case sem::BuiltinType::kPack2X16Unorm:
+        case builtin::Function::kPack2X16Unorm:
             out += "pack_float_to_unorm2x16";
             break;
-        case sem::BuiltinType::kReverseBits:
+        case builtin::Function::kReverseBits:
             out += "reverse_bits";
             break;
-        case sem::BuiltinType::kRound:
+        case builtin::Function::kRound:
             out += "rint";
             break;
-        case sem::BuiltinType::kSmoothstep:
+        case builtin::Function::kSmoothstep:
             out += "smoothstep";
             break;
-        case sem::BuiltinType::kInverseSqrt:
+        case builtin::Function::kInverseSqrt:
             out += "rsqrt";
             break;
-        case sem::BuiltinType::kUnpack4X8Snorm:
+        case builtin::Function::kUnpack4X8Snorm:
             out += "unpack_snorm4x8_to_float";
             break;
-        case sem::BuiltinType::kUnpack4X8Unorm:
+        case builtin::Function::kUnpack4X8Unorm:
             out += "unpack_unorm4x8_to_float";
             break;
-        case sem::BuiltinType::kUnpack2X16Snorm:
+        case builtin::Function::kUnpack2X16Snorm:
             out += "unpack_snorm2x16_to_float";
             break;
-        case sem::BuiltinType::kUnpack2X16Unorm:
+        case builtin::Function::kUnpack2X16Unorm:
             out += "unpack_unorm2x16_to_float";
             break;
-        case sem::BuiltinType::kArrayLength:
+        case builtin::Function::kArrayLength:
             diagnostics_.add_error(
                 diag::System::Writer,
                 "Unable to translate builtin: " + std::string(builtin->str()) +
@@ -1707,8 +1709,8 @@
 
             ScopedParen sp(out);
 
-            if (constant->AllEqual()) {
-                if (!EmitConstant(out, constant->Index(0))) {
+            if (auto* splat = constant->As<constant::Splat>()) {
+                if (!EmitConstant(out, splat->el)) {
                     return false;
                 }
                 return true;
@@ -3258,7 +3260,7 @@
         TextBuffer b;
         TINT_DEFER(helpers_.Append(b));
 
-        auto fn_name = UniqueIdentifier(std::string("tint_") + sem::str(builtin->Type()));
+        auto fn_name = UniqueIdentifier(std::string("tint_") + builtin::str(builtin->Type()));
         std::vector<std::string> parameter_names;
         {
             auto decl = line(&b);
diff --git a/src/tint/writer/msl/generator_impl_builtin_test.cc b/src/tint/writer/msl/generator_impl_builtin_test.cc
index 08a2d25..6c70abe 100644
--- a/src/tint/writer/msl/generator_impl_builtin_test.cc
+++ b/src/tint/writer/msl/generator_impl_builtin_test.cc
@@ -22,8 +22,6 @@
 namespace tint::writer::msl {
 namespace {
 
-using BuiltinType = sem::BuiltinType;
-
 using MslGeneratorImplTest = TestHelper;
 
 enum class CallParamType {
@@ -34,7 +32,7 @@
 };
 
 struct BuiltinData {
-    BuiltinType builtin;
+    builtin::Function builtin;
     CallParamType type;
     const char* msl_name;
 };
@@ -58,88 +56,88 @@
     return out;
 }
 
-const ast::CallExpression* GenerateCall(BuiltinType builtin,
+const ast::CallExpression* GenerateCall(builtin::Function builtin,
                                         CallParamType type,
                                         ProgramBuilder* builder) {
     std::string name;
     utils::StringStream str;
     str << name << builtin;
     switch (builtin) {
-        case BuiltinType::kAcos:
-        case BuiltinType::kAsin:
-        case BuiltinType::kAtan:
-        case BuiltinType::kCeil:
-        case BuiltinType::kCos:
-        case BuiltinType::kCosh:
-        case BuiltinType::kDpdx:
-        case BuiltinType::kDpdxCoarse:
-        case BuiltinType::kDpdxFine:
-        case BuiltinType::kDpdy:
-        case BuiltinType::kDpdyCoarse:
-        case BuiltinType::kDpdyFine:
-        case BuiltinType::kExp:
-        case BuiltinType::kExp2:
-        case BuiltinType::kFloor:
-        case BuiltinType::kFract:
-        case BuiltinType::kFwidth:
-        case BuiltinType::kFwidthCoarse:
-        case BuiltinType::kFwidthFine:
-        case BuiltinType::kInverseSqrt:
-        case BuiltinType::kLength:
-        case BuiltinType::kLog:
-        case BuiltinType::kLog2:
-        case BuiltinType::kNormalize:
-        case BuiltinType::kRound:
-        case BuiltinType::kSin:
-        case BuiltinType::kSinh:
-        case BuiltinType::kSqrt:
-        case BuiltinType::kTan:
-        case BuiltinType::kTanh:
-        case BuiltinType::kTrunc:
-        case BuiltinType::kSign:
+        case builtin::Function::kAcos:
+        case builtin::Function::kAsin:
+        case builtin::Function::kAtan:
+        case builtin::Function::kCeil:
+        case builtin::Function::kCos:
+        case builtin::Function::kCosh:
+        case builtin::Function::kDpdx:
+        case builtin::Function::kDpdxCoarse:
+        case builtin::Function::kDpdxFine:
+        case builtin::Function::kDpdy:
+        case builtin::Function::kDpdyCoarse:
+        case builtin::Function::kDpdyFine:
+        case builtin::Function::kExp:
+        case builtin::Function::kExp2:
+        case builtin::Function::kFloor:
+        case builtin::Function::kFract:
+        case builtin::Function::kFwidth:
+        case builtin::Function::kFwidthCoarse:
+        case builtin::Function::kFwidthFine:
+        case builtin::Function::kInverseSqrt:
+        case builtin::Function::kLength:
+        case builtin::Function::kLog:
+        case builtin::Function::kLog2:
+        case builtin::Function::kNormalize:
+        case builtin::Function::kRound:
+        case builtin::Function::kSin:
+        case builtin::Function::kSinh:
+        case builtin::Function::kSqrt:
+        case builtin::Function::kTan:
+        case builtin::Function::kTanh:
+        case builtin::Function::kTrunc:
+        case builtin::Function::kSign:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2");
             } else {
                 return builder->Call(str.str(), "f2");
             }
-        case BuiltinType::kLdexp:
+        case builtin::Function::kLdexp:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "i2");
             } else {
                 return builder->Call(str.str(), "f2", "i2");
             }
-        case BuiltinType::kAtan2:
-        case BuiltinType::kDot:
-        case BuiltinType::kDistance:
-        case BuiltinType::kPow:
-        case BuiltinType::kReflect:
-        case BuiltinType::kStep:
+        case builtin::Function::kAtan2:
+        case builtin::Function::kDot:
+        case builtin::Function::kDistance:
+        case builtin::Function::kPow:
+        case builtin::Function::kReflect:
+        case builtin::Function::kStep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2");
             }
-        case BuiltinType::kStorageBarrier:
+        case builtin::Function::kStorageBarrier:
             return builder->Call(str.str());
-        case BuiltinType::kCross:
+        case builtin::Function::kCross:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h3", "h3");
             } else {
                 return builder->Call(str.str(), "f3", "f3");
             }
-        case BuiltinType::kFma:
-        case BuiltinType::kMix:
-        case BuiltinType::kFaceForward:
-        case BuiltinType::kSmoothstep:
+        case builtin::Function::kFma:
+        case builtin::Function::kMix:
+        case builtin::Function::kFaceForward:
+        case builtin::Function::kSmoothstep:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "h2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             }
-        case BuiltinType::kAll:
-        case BuiltinType::kAny:
+        case builtin::Function::kAll:
+        case builtin::Function::kAny:
             return builder->Call(str.str(), "b2");
-        case BuiltinType::kAbs:
+        case builtin::Function::kAbs:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2");
             } else if (type == CallParamType::kF16) {
@@ -147,17 +145,17 @@
             } else {
                 return builder->Call(str.str(), "u2");
             }
-        case BuiltinType::kCountLeadingZeros:
-        case BuiltinType::kCountOneBits:
-        case BuiltinType::kCountTrailingZeros:
-        case BuiltinType::kReverseBits:
+        case builtin::Function::kCountLeadingZeros:
+        case builtin::Function::kCountOneBits:
+        case builtin::Function::kCountTrailingZeros:
+        case builtin::Function::kReverseBits:
             return builder->Call(str.str(), "u2");
-        case BuiltinType::kExtractBits:
+        case builtin::Function::kExtractBits:
             return builder->Call(str.str(), "u2", "u1", "u1");
-        case BuiltinType::kInsertBits:
+        case builtin::Function::kInsertBits:
             return builder->Call(str.str(), "u2", "u2", "u1", "u1");
-        case BuiltinType::kMax:
-        case BuiltinType::kMin:
+        case builtin::Function::kMax:
+        case builtin::Function::kMin:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -165,7 +163,7 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2");
             }
-        case BuiltinType::kClamp:
+        case builtin::Function::kClamp:
             if (type == CallParamType::kF32) {
                 return builder->Call(str.str(), "f2", "f2", "f2");
             } else if (type == CallParamType::kF16) {
@@ -173,32 +171,32 @@
             } else {
                 return builder->Call(str.str(), "u2", "u2", "u2");
             }
-        case BuiltinType::kSelect:
+        case builtin::Function::kSelect:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "h2", "h2", "b2");
             } else {
                 return builder->Call(str.str(), "f2", "f2", "b2");
             }
-        case BuiltinType::kDeterminant:
+        case builtin::Function::kDeterminant:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm2x2");
             } else {
                 return builder->Call(str.str(), "m2x2");
             }
-        case BuiltinType::kPack2X16Snorm:
-        case BuiltinType::kPack2X16Unorm:
+        case builtin::Function::kPack2X16Snorm:
+        case builtin::Function::kPack2X16Unorm:
             return builder->Call(str.str(), "f2");
-        case BuiltinType::kPack4X8Snorm:
-        case BuiltinType::kPack4X8Unorm:
+        case builtin::Function::kPack4X8Snorm:
+        case builtin::Function::kPack4X8Unorm:
             return builder->Call(str.str(), "f4");
-        case BuiltinType::kUnpack4X8Snorm:
-        case BuiltinType::kUnpack4X8Unorm:
-        case BuiltinType::kUnpack2X16Snorm:
-        case BuiltinType::kUnpack2X16Unorm:
+        case builtin::Function::kUnpack4X8Snorm:
+        case builtin::Function::kUnpack4X8Unorm:
+        case builtin::Function::kUnpack2X16Snorm:
+        case builtin::Function::kUnpack2X16Unorm:
             return builder->Call(str.str(), "u1");
-        case BuiltinType::kWorkgroupBarrier:
+        case builtin::Function::kWorkgroupBarrier:
             return builder->Call(str.str());
-        case BuiltinType::kTranspose:
+        case builtin::Function::kTranspose:
             if (type == CallParamType::kF16) {
                 return builder->Call(str.str(), "hm3x2");
             } else {
@@ -254,120 +252,126 @@
     MslBuiltinTest,
     testing::Values(
         /* Logical built-in */
-        BuiltinData{BuiltinType::kAll, CallParamType::kBool, "all"},
-        BuiltinData{BuiltinType::kAny, CallParamType::kBool, "any"},
-        BuiltinData{BuiltinType::kSelect, CallParamType::kF32, "select"},
+        BuiltinData{builtin::Function::kAll, CallParamType::kBool, "all"},
+        BuiltinData{builtin::Function::kAny, CallParamType::kBool, "any"},
+        BuiltinData{builtin::Function::kSelect, CallParamType::kF32, "select"},
         /* Float built-in */
-        BuiltinData{BuiltinType::kAbs, CallParamType::kF32, "fabs"},
-        BuiltinData{BuiltinType::kAbs, CallParamType::kF16, "fabs"},
-        BuiltinData{BuiltinType::kAcos, CallParamType::kF32, "acos"},
-        BuiltinData{BuiltinType::kAcos, CallParamType::kF16, "acos"},
-        BuiltinData{BuiltinType::kAsin, CallParamType::kF32, "asin"},
-        BuiltinData{BuiltinType::kAsin, CallParamType::kF16, "asin"},
-        BuiltinData{BuiltinType::kAtan, CallParamType::kF32, "atan"},
-        BuiltinData{BuiltinType::kAtan, CallParamType::kF16, "atan"},
-        BuiltinData{BuiltinType::kAtan2, CallParamType::kF32, "atan2"},
-        BuiltinData{BuiltinType::kAtan2, CallParamType::kF16, "atan2"},
-        BuiltinData{BuiltinType::kCeil, CallParamType::kF32, "ceil"},
-        BuiltinData{BuiltinType::kCeil, CallParamType::kF16, "ceil"},
-        BuiltinData{BuiltinType::kClamp, CallParamType::kF32, "clamp"},
-        BuiltinData{BuiltinType::kClamp, CallParamType::kF16, "clamp"},
-        BuiltinData{BuiltinType::kCos, CallParamType::kF32, "cos"},
-        BuiltinData{BuiltinType::kCos, CallParamType::kF16, "cos"},
-        BuiltinData{BuiltinType::kCosh, CallParamType::kF32, "cosh"},
-        BuiltinData{BuiltinType::kCosh, CallParamType::kF16, "cosh"},
-        BuiltinData{BuiltinType::kCross, CallParamType::kF32, "cross"},
-        BuiltinData{BuiltinType::kCross, CallParamType::kF16, "cross"},
-        BuiltinData{BuiltinType::kDistance, CallParamType::kF32, "distance"},
-        BuiltinData{BuiltinType::kDistance, CallParamType::kF16, "distance"},
-        BuiltinData{BuiltinType::kExp, CallParamType::kF32, "exp"},
-        BuiltinData{BuiltinType::kExp, CallParamType::kF16, "exp"},
-        BuiltinData{BuiltinType::kExp2, CallParamType::kF32, "exp2"},
-        BuiltinData{BuiltinType::kExp2, CallParamType::kF16, "exp2"},
-        BuiltinData{BuiltinType::kFaceForward, CallParamType::kF32, "faceforward"},
-        BuiltinData{BuiltinType::kFaceForward, CallParamType::kF16, "faceforward"},
-        BuiltinData{BuiltinType::kFloor, CallParamType::kF32, "floor"},
-        BuiltinData{BuiltinType::kFloor, CallParamType::kF16, "floor"},
-        BuiltinData{BuiltinType::kFma, CallParamType::kF32, "fma"},
-        BuiltinData{BuiltinType::kFma, CallParamType::kF16, "fma"},
-        BuiltinData{BuiltinType::kFract, CallParamType::kF32, "fract"},
-        BuiltinData{BuiltinType::kFract, CallParamType::kF16, "fract"},
-        BuiltinData{BuiltinType::kInverseSqrt, CallParamType::kF32, "rsqrt"},
-        BuiltinData{BuiltinType::kInverseSqrt, CallParamType::kF16, "rsqrt"},
-        BuiltinData{BuiltinType::kLdexp, CallParamType::kF32, "ldexp"},
-        BuiltinData{BuiltinType::kLdexp, CallParamType::kF16, "ldexp"},
-        BuiltinData{BuiltinType::kLength, CallParamType::kF32, "length"},
-        BuiltinData{BuiltinType::kLength, CallParamType::kF16, "length"},
-        BuiltinData{BuiltinType::kLog, CallParamType::kF32, "log"},
-        BuiltinData{BuiltinType::kLog, CallParamType::kF16, "log"},
-        BuiltinData{BuiltinType::kLog2, CallParamType::kF32, "log2"},
-        BuiltinData{BuiltinType::kLog2, CallParamType::kF16, "log2"},
-        BuiltinData{BuiltinType::kMax, CallParamType::kF32, "fmax"},
-        BuiltinData{BuiltinType::kMax, CallParamType::kF16, "fmax"},
-        BuiltinData{BuiltinType::kMin, CallParamType::kF32, "fmin"},
-        BuiltinData{BuiltinType::kMin, CallParamType::kF16, "fmin"},
-        BuiltinData{BuiltinType::kNormalize, CallParamType::kF32, "normalize"},
-        BuiltinData{BuiltinType::kNormalize, CallParamType::kF16, "normalize"},
-        BuiltinData{BuiltinType::kPow, CallParamType::kF32, "pow"},
-        BuiltinData{BuiltinType::kPow, CallParamType::kF16, "pow"},
-        BuiltinData{BuiltinType::kReflect, CallParamType::kF32, "reflect"},
-        BuiltinData{BuiltinType::kReflect, CallParamType::kF16, "reflect"},
-        BuiltinData{BuiltinType::kSign, CallParamType::kF32, "sign"},
-        BuiltinData{BuiltinType::kSign, CallParamType::kF16, "sign"},
-        BuiltinData{BuiltinType::kSin, CallParamType::kF32, "sin"},
-        BuiltinData{BuiltinType::kSin, CallParamType::kF16, "sin"},
-        BuiltinData{BuiltinType::kSinh, CallParamType::kF32, "sinh"},
-        BuiltinData{BuiltinType::kSinh, CallParamType::kF16, "sinh"},
-        BuiltinData{BuiltinType::kSmoothstep, CallParamType::kF32, "smoothstep"},
-        BuiltinData{BuiltinType::kSmoothstep, CallParamType::kF16, "smoothstep"},
-        BuiltinData{BuiltinType::kSqrt, CallParamType::kF32, "sqrt"},
-        BuiltinData{BuiltinType::kSqrt, CallParamType::kF16, "sqrt"},
-        BuiltinData{BuiltinType::kStep, CallParamType::kF32, "step"},
-        BuiltinData{BuiltinType::kStep, CallParamType::kF16, "step"},
-        BuiltinData{BuiltinType::kTan, CallParamType::kF32, "tan"},
-        BuiltinData{BuiltinType::kTan, CallParamType::kF16, "tan"},
-        BuiltinData{BuiltinType::kTanh, CallParamType::kF32, "tanh"},
-        BuiltinData{BuiltinType::kTanh, CallParamType::kF16, "tanh"},
-        BuiltinData{BuiltinType::kTrunc, CallParamType::kF32, "trunc"},
-        BuiltinData{BuiltinType::kTrunc, CallParamType::kF16, "trunc"},
+        BuiltinData{builtin::Function::kAbs, CallParamType::kF32, "fabs"},
+        BuiltinData{builtin::Function::kAbs, CallParamType::kF16, "fabs"},
+        BuiltinData{builtin::Function::kAcos, CallParamType::kF32, "acos"},
+        BuiltinData{builtin::Function::kAcos, CallParamType::kF16, "acos"},
+        BuiltinData{builtin::Function::kAsin, CallParamType::kF32, "asin"},
+        BuiltinData{builtin::Function::kAsin, CallParamType::kF16, "asin"},
+        BuiltinData{builtin::Function::kAtan, CallParamType::kF32, "atan"},
+        BuiltinData{builtin::Function::kAtan, CallParamType::kF16, "atan"},
+        BuiltinData{builtin::Function::kAtan2, CallParamType::kF32, "atan2"},
+        BuiltinData{builtin::Function::kAtan2, CallParamType::kF16, "atan2"},
+        BuiltinData{builtin::Function::kCeil, CallParamType::kF32, "ceil"},
+        BuiltinData{builtin::Function::kCeil, CallParamType::kF16, "ceil"},
+        BuiltinData{builtin::Function::kClamp, CallParamType::kF32, "clamp"},
+        BuiltinData{builtin::Function::kClamp, CallParamType::kF16, "clamp"},
+        BuiltinData{builtin::Function::kCos, CallParamType::kF32, "cos"},
+        BuiltinData{builtin::Function::kCos, CallParamType::kF16, "cos"},
+        BuiltinData{builtin::Function::kCosh, CallParamType::kF32, "cosh"},
+        BuiltinData{builtin::Function::kCosh, CallParamType::kF16, "cosh"},
+        BuiltinData{builtin::Function::kCross, CallParamType::kF32, "cross"},
+        BuiltinData{builtin::Function::kCross, CallParamType::kF16, "cross"},
+        BuiltinData{builtin::Function::kDistance, CallParamType::kF32, "distance"},
+        BuiltinData{builtin::Function::kDistance, CallParamType::kF16, "distance"},
+        BuiltinData{builtin::Function::kExp, CallParamType::kF32, "exp"},
+        BuiltinData{builtin::Function::kExp, CallParamType::kF16, "exp"},
+        BuiltinData{builtin::Function::kExp2, CallParamType::kF32, "exp2"},
+        BuiltinData{builtin::Function::kExp2, CallParamType::kF16, "exp2"},
+        BuiltinData{builtin::Function::kFaceForward, CallParamType::kF32, "faceforward"},
+        BuiltinData{builtin::Function::kFaceForward, CallParamType::kF16, "faceforward"},
+        BuiltinData{builtin::Function::kFloor, CallParamType::kF32, "floor"},
+        BuiltinData{builtin::Function::kFloor, CallParamType::kF16, "floor"},
+        BuiltinData{builtin::Function::kFma, CallParamType::kF32, "fma"},
+        BuiltinData{builtin::Function::kFma, CallParamType::kF16, "fma"},
+        BuiltinData{builtin::Function::kFract, CallParamType::kF32, "fract"},
+        BuiltinData{builtin::Function::kFract, CallParamType::kF16, "fract"},
+        BuiltinData{builtin::Function::kInverseSqrt, CallParamType::kF32, "rsqrt"},
+        BuiltinData{builtin::Function::kInverseSqrt, CallParamType::kF16, "rsqrt"},
+        BuiltinData{builtin::Function::kLdexp, CallParamType::kF32, "ldexp"},
+        BuiltinData{builtin::Function::kLdexp, CallParamType::kF16, "ldexp"},
+        BuiltinData{builtin::Function::kLength, CallParamType::kF32, "length"},
+        BuiltinData{builtin::Function::kLength, CallParamType::kF16, "length"},
+        BuiltinData{builtin::Function::kLog, CallParamType::kF32, "log"},
+        BuiltinData{builtin::Function::kLog, CallParamType::kF16, "log"},
+        BuiltinData{builtin::Function::kLog2, CallParamType::kF32, "log2"},
+        BuiltinData{builtin::Function::kLog2, CallParamType::kF16, "log2"},
+        BuiltinData{builtin::Function::kMax, CallParamType::kF32, "fmax"},
+        BuiltinData{builtin::Function::kMax, CallParamType::kF16, "fmax"},
+        BuiltinData{builtin::Function::kMin, CallParamType::kF32, "fmin"},
+        BuiltinData{builtin::Function::kMin, CallParamType::kF16, "fmin"},
+        BuiltinData{builtin::Function::kNormalize, CallParamType::kF32, "normalize"},
+        BuiltinData{builtin::Function::kNormalize, CallParamType::kF16, "normalize"},
+        BuiltinData{builtin::Function::kPow, CallParamType::kF32, "pow"},
+        BuiltinData{builtin::Function::kPow, CallParamType::kF16, "pow"},
+        BuiltinData{builtin::Function::kReflect, CallParamType::kF32, "reflect"},
+        BuiltinData{builtin::Function::kReflect, CallParamType::kF16, "reflect"},
+        BuiltinData{builtin::Function::kSign, CallParamType::kF32, "sign"},
+        BuiltinData{builtin::Function::kSign, CallParamType::kF16, "sign"},
+        BuiltinData{builtin::Function::kSin, CallParamType::kF32, "sin"},
+        BuiltinData{builtin::Function::kSin, CallParamType::kF16, "sin"},
+        BuiltinData{builtin::Function::kSinh, CallParamType::kF32, "sinh"},
+        BuiltinData{builtin::Function::kSinh, CallParamType::kF16, "sinh"},
+        BuiltinData{builtin::Function::kSmoothstep, CallParamType::kF32, "smoothstep"},
+        BuiltinData{builtin::Function::kSmoothstep, CallParamType::kF16, "smoothstep"},
+        BuiltinData{builtin::Function::kSqrt, CallParamType::kF32, "sqrt"},
+        BuiltinData{builtin::Function::kSqrt, CallParamType::kF16, "sqrt"},
+        BuiltinData{builtin::Function::kStep, CallParamType::kF32, "step"},
+        BuiltinData{builtin::Function::kStep, CallParamType::kF16, "step"},
+        BuiltinData{builtin::Function::kTan, CallParamType::kF32, "tan"},
+        BuiltinData{builtin::Function::kTan, CallParamType::kF16, "tan"},
+        BuiltinData{builtin::Function::kTanh, CallParamType::kF32, "tanh"},
+        BuiltinData{builtin::Function::kTanh, CallParamType::kF16, "tanh"},
+        BuiltinData{builtin::Function::kTrunc, CallParamType::kF32, "trunc"},
+        BuiltinData{builtin::Function::kTrunc, CallParamType::kF16, "trunc"},
         /* Integer built-in */
-        BuiltinData{BuiltinType::kAbs, CallParamType::kU32, "abs"},
-        BuiltinData{BuiltinType::kClamp, CallParamType::kU32, "clamp"},
-        BuiltinData{BuiltinType::kCountLeadingZeros, CallParamType::kU32, "clz"},
-        BuiltinData{BuiltinType::kCountOneBits, CallParamType::kU32, "popcount"},
-        BuiltinData{BuiltinType::kCountTrailingZeros, CallParamType::kU32, "ctz"},
-        BuiltinData{BuiltinType::kExtractBits, CallParamType::kU32, "extract_bits"},
-        BuiltinData{BuiltinType::kInsertBits, CallParamType::kU32, "insert_bits"},
-        BuiltinData{BuiltinType::kMax, CallParamType::kU32, "max"},
-        BuiltinData{BuiltinType::kMin, CallParamType::kU32, "min"},
-        BuiltinData{BuiltinType::kReverseBits, CallParamType::kU32, "reverse_bits"},
-        BuiltinData{BuiltinType::kRound, CallParamType::kU32, "rint"},
+        BuiltinData{builtin::Function::kAbs, CallParamType::kU32, "abs"},
+        BuiltinData{builtin::Function::kClamp, CallParamType::kU32, "clamp"},
+        BuiltinData{builtin::Function::kCountLeadingZeros, CallParamType::kU32, "clz"},
+        BuiltinData{builtin::Function::kCountOneBits, CallParamType::kU32, "popcount"},
+        BuiltinData{builtin::Function::kCountTrailingZeros, CallParamType::kU32, "ctz"},
+        BuiltinData{builtin::Function::kExtractBits, CallParamType::kU32, "extract_bits"},
+        BuiltinData{builtin::Function::kInsertBits, CallParamType::kU32, "insert_bits"},
+        BuiltinData{builtin::Function::kMax, CallParamType::kU32, "max"},
+        BuiltinData{builtin::Function::kMin, CallParamType::kU32, "min"},
+        BuiltinData{builtin::Function::kReverseBits, CallParamType::kU32, "reverse_bits"},
+        BuiltinData{builtin::Function::kRound, CallParamType::kU32, "rint"},
         /* Matrix built-in */
-        BuiltinData{BuiltinType::kDeterminant, CallParamType::kF32, "determinant"},
-        BuiltinData{BuiltinType::kTranspose, CallParamType::kF32, "transpose"},
+        BuiltinData{builtin::Function::kDeterminant, CallParamType::kF32, "determinant"},
+        BuiltinData{builtin::Function::kTranspose, CallParamType::kF32, "transpose"},
         /* Vector built-in */
-        BuiltinData{BuiltinType::kDot, CallParamType::kF32, "dot"},
+        BuiltinData{builtin::Function::kDot, CallParamType::kF32, "dot"},
         /* Derivate built-in */
-        BuiltinData{BuiltinType::kDpdx, CallParamType::kF32, "dfdx"},
-        BuiltinData{BuiltinType::kDpdxCoarse, CallParamType::kF32, "dfdx"},
-        BuiltinData{BuiltinType::kDpdxFine, CallParamType::kF32, "dfdx"},
-        BuiltinData{BuiltinType::kDpdy, CallParamType::kF32, "dfdy"},
-        BuiltinData{BuiltinType::kDpdyCoarse, CallParamType::kF32, "dfdy"},
-        BuiltinData{BuiltinType::kDpdyFine, CallParamType::kF32, "dfdy"},
-        BuiltinData{BuiltinType::kFwidth, CallParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kFwidthCoarse, CallParamType::kF32, "fwidth"},
-        BuiltinData{BuiltinType::kFwidthFine, CallParamType::kF32, "fwidth"},
+        BuiltinData{builtin::Function::kDpdx, CallParamType::kF32, "dfdx"},
+        BuiltinData{builtin::Function::kDpdxCoarse, CallParamType::kF32, "dfdx"},
+        BuiltinData{builtin::Function::kDpdxFine, CallParamType::kF32, "dfdx"},
+        BuiltinData{builtin::Function::kDpdy, CallParamType::kF32, "dfdy"},
+        BuiltinData{builtin::Function::kDpdyCoarse, CallParamType::kF32, "dfdy"},
+        BuiltinData{builtin::Function::kDpdyFine, CallParamType::kF32, "dfdy"},
+        BuiltinData{builtin::Function::kFwidth, CallParamType::kF32, "fwidth"},
+        BuiltinData{builtin::Function::kFwidthCoarse, CallParamType::kF32, "fwidth"},
+        BuiltinData{builtin::Function::kFwidthFine, CallParamType::kF32, "fwidth"},
         /* Data packing builtin */
-        BuiltinData{BuiltinType::kPack4X8Snorm, CallParamType::kF32, "pack_float_to_snorm4x8"},
-        BuiltinData{BuiltinType::kPack4X8Unorm, CallParamType::kF32, "pack_float_to_unorm4x8"},
-        BuiltinData{BuiltinType::kPack2X16Snorm, CallParamType::kF32, "pack_float_to_snorm2x16"},
-        BuiltinData{BuiltinType::kPack2X16Unorm, CallParamType::kF32, "pack_float_to_unorm2x16"},
+        BuiltinData{builtin::Function::kPack4X8Snorm, CallParamType::kF32,
+                    "pack_float_to_snorm4x8"},
+        BuiltinData{builtin::Function::kPack4X8Unorm, CallParamType::kF32,
+                    "pack_float_to_unorm4x8"},
+        BuiltinData{builtin::Function::kPack2X16Snorm, CallParamType::kF32,
+                    "pack_float_to_snorm2x16"},
+        BuiltinData{builtin::Function::kPack2X16Unorm, CallParamType::kF32,
+                    "pack_float_to_unorm2x16"},
         /* Data unpacking builtin */
-        BuiltinData{BuiltinType::kUnpack4X8Snorm, CallParamType::kU32, "unpack_snorm4x8_to_float"},
-        BuiltinData{BuiltinType::kUnpack4X8Unorm, CallParamType::kU32, "unpack_unorm4x8_to_float"},
-        BuiltinData{BuiltinType::kUnpack2X16Snorm, CallParamType::kU32,
+        BuiltinData{builtin::Function::kUnpack4X8Snorm, CallParamType::kU32,
+                    "unpack_snorm4x8_to_float"},
+        BuiltinData{builtin::Function::kUnpack4X8Unorm, CallParamType::kU32,
+                    "unpack_unorm4x8_to_float"},
+        BuiltinData{builtin::Function::kUnpack2X16Snorm, CallParamType::kU32,
                     "unpack_snorm2x16_to_float"},
-        BuiltinData{BuiltinType::kUnpack2X16Unorm, CallParamType::kU32,
+        BuiltinData{builtin::Function::kUnpack2X16Unorm, CallParamType::kU32,
                     "unpack_unorm2x16_to_float"}));
 
 TEST_F(MslGeneratorImplTest, Builtin_Call) {
diff --git a/src/tint/writer/msl/generator_impl_builtin_texture_test.cc b/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
index 7beee1a..730fe99 100644
--- a/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
+++ b/src/tint/writer/msl/generator_impl_builtin_texture_test.cc
@@ -90,7 +90,9 @@
         case ValidTextureOverload::kGatherCompareDepthCubeArrayF32:
             return R"(Texture.gather_compare(Sampler, float3(1.0f, 2.0f, 3.0f), 4u, 5.0f))";
         case ValidTextureOverload::kNumLayers2dArray:
+        case ValidTextureOverload::kNumLayersCubeArray:
         case ValidTextureOverload::kNumLayersDepth2dArray:
+        case ValidTextureOverload::kNumLayersDepthCubeArray:
         case ValidTextureOverload::kNumLayersStorageWO2dArray:
             return R"(Texture.get_array_size())";
         case ValidTextureOverload::kNumLevels2d:
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index 40f98d8..ae14acd 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -56,8 +56,6 @@
 namespace tint::writer::spirv {
 namespace {
 
-using BuiltinType = sem::BuiltinType;
-
 const char kGLSLstd450[] = "GLSL.std.450";
 
 uint32_t size_of(const InstructionList& instructions) {
@@ -102,23 +100,23 @@
 
 uint32_t builtin_to_glsl_method(const sem::Builtin* builtin) {
     switch (builtin->Type()) {
-        case BuiltinType::kAcos:
+        case builtin::Function::kAcos:
             return GLSLstd450Acos;
-        case BuiltinType::kAcosh:
+        case builtin::Function::kAcosh:
             return GLSLstd450Acosh;
-        case BuiltinType::kAsin:
+        case builtin::Function::kAsin:
             return GLSLstd450Asin;
-        case BuiltinType::kAsinh:
+        case builtin::Function::kAsinh:
             return GLSLstd450Asinh;
-        case BuiltinType::kAtan:
+        case builtin::Function::kAtan:
             return GLSLstd450Atan;
-        case BuiltinType::kAtan2:
+        case builtin::Function::kAtan2:
             return GLSLstd450Atan2;
-        case BuiltinType::kAtanh:
+        case builtin::Function::kAtanh:
             return GLSLstd450Atanh;
-        case BuiltinType::kCeil:
+        case builtin::Function::kCeil:
             return GLSLstd450Ceil;
-        case BuiltinType::kClamp:
+        case builtin::Function::kClamp:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 return GLSLstd450NClamp;
             } else if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
@@ -126,43 +124,43 @@
             } else {
                 return GLSLstd450SClamp;
             }
-        case BuiltinType::kCos:
+        case builtin::Function::kCos:
             return GLSLstd450Cos;
-        case BuiltinType::kCosh:
+        case builtin::Function::kCosh:
             return GLSLstd450Cosh;
-        case BuiltinType::kCross:
+        case builtin::Function::kCross:
             return GLSLstd450Cross;
-        case BuiltinType::kDegrees:
+        case builtin::Function::kDegrees:
             return GLSLstd450Degrees;
-        case BuiltinType::kDeterminant:
+        case builtin::Function::kDeterminant:
             return GLSLstd450Determinant;
-        case BuiltinType::kDistance:
+        case builtin::Function::kDistance:
             return GLSLstd450Distance;
-        case BuiltinType::kExp:
+        case builtin::Function::kExp:
             return GLSLstd450Exp;
-        case BuiltinType::kExp2:
+        case builtin::Function::kExp2:
             return GLSLstd450Exp2;
-        case BuiltinType::kFaceForward:
+        case builtin::Function::kFaceForward:
             return GLSLstd450FaceForward;
-        case BuiltinType::kFloor:
+        case builtin::Function::kFloor:
             return GLSLstd450Floor;
-        case BuiltinType::kFma:
+        case builtin::Function::kFma:
             return GLSLstd450Fma;
-        case BuiltinType::kFract:
+        case builtin::Function::kFract:
             return GLSLstd450Fract;
-        case BuiltinType::kFrexp:
+        case builtin::Function::kFrexp:
             return GLSLstd450FrexpStruct;
-        case BuiltinType::kInverseSqrt:
+        case builtin::Function::kInverseSqrt:
             return GLSLstd450InverseSqrt;
-        case BuiltinType::kLdexp:
+        case builtin::Function::kLdexp:
             return GLSLstd450Ldexp;
-        case BuiltinType::kLength:
+        case builtin::Function::kLength:
             return GLSLstd450Length;
-        case BuiltinType::kLog:
+        case builtin::Function::kLog:
             return GLSLstd450Log;
-        case BuiltinType::kLog2:
+        case builtin::Function::kLog2:
             return GLSLstd450Log2;
-        case BuiltinType::kMax:
+        case builtin::Function::kMax:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 return GLSLstd450NMax;
             } else if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
@@ -170,7 +168,7 @@
             } else {
                 return GLSLstd450SMax;
             }
-        case BuiltinType::kMin:
+        case builtin::Function::kMin:
             if (builtin->ReturnType()->is_float_scalar_or_vector()) {
                 return GLSLstd450NMin;
             } else if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
@@ -178,63 +176,63 @@
             } else {
                 return GLSLstd450SMin;
             }
-        case BuiltinType::kMix:
+        case builtin::Function::kMix:
             return GLSLstd450FMix;
-        case BuiltinType::kModf:
+        case builtin::Function::kModf:
             return GLSLstd450ModfStruct;
-        case BuiltinType::kNormalize:
+        case builtin::Function::kNormalize:
             return GLSLstd450Normalize;
-        case BuiltinType::kPack4X8Snorm:
+        case builtin::Function::kPack4X8Snorm:
             return GLSLstd450PackSnorm4x8;
-        case BuiltinType::kPack4X8Unorm:
+        case builtin::Function::kPack4X8Unorm:
             return GLSLstd450PackUnorm4x8;
-        case BuiltinType::kPack2X16Snorm:
+        case builtin::Function::kPack2X16Snorm:
             return GLSLstd450PackSnorm2x16;
-        case BuiltinType::kPack2X16Unorm:
+        case builtin::Function::kPack2X16Unorm:
             return GLSLstd450PackUnorm2x16;
-        case BuiltinType::kPack2X16Float:
+        case builtin::Function::kPack2X16Float:
             return GLSLstd450PackHalf2x16;
-        case BuiltinType::kPow:
+        case builtin::Function::kPow:
             return GLSLstd450Pow;
-        case BuiltinType::kRadians:
+        case builtin::Function::kRadians:
             return GLSLstd450Radians;
-        case BuiltinType::kReflect:
+        case builtin::Function::kReflect:
             return GLSLstd450Reflect;
-        case BuiltinType::kRefract:
+        case builtin::Function::kRefract:
             return GLSLstd450Refract;
-        case BuiltinType::kRound:
+        case builtin::Function::kRound:
             return GLSLstd450RoundEven;
-        case BuiltinType::kSign:
+        case builtin::Function::kSign:
             if (builtin->ReturnType()->is_signed_integer_scalar_or_vector()) {
                 return GLSLstd450SSign;
             } else {
                 return GLSLstd450FSign;
             }
-        case BuiltinType::kSin:
+        case builtin::Function::kSin:
             return GLSLstd450Sin;
-        case BuiltinType::kSinh:
+        case builtin::Function::kSinh:
             return GLSLstd450Sinh;
-        case BuiltinType::kSmoothstep:
+        case builtin::Function::kSmoothstep:
             return GLSLstd450SmoothStep;
-        case BuiltinType::kSqrt:
+        case builtin::Function::kSqrt:
             return GLSLstd450Sqrt;
-        case BuiltinType::kStep:
+        case builtin::Function::kStep:
             return GLSLstd450Step;
-        case BuiltinType::kTan:
+        case builtin::Function::kTan:
             return GLSLstd450Tan;
-        case BuiltinType::kTanh:
+        case builtin::Function::kTanh:
             return GLSLstd450Tanh;
-        case BuiltinType::kTrunc:
+        case builtin::Function::kTrunc:
             return GLSLstd450Trunc;
-        case BuiltinType::kUnpack4X8Snorm:
+        case builtin::Function::kUnpack4X8Snorm:
             return GLSLstd450UnpackSnorm4x8;
-        case BuiltinType::kUnpack4X8Unorm:
+        case builtin::Function::kUnpack4X8Unorm:
             return GLSLstd450UnpackUnorm4x8;
-        case BuiltinType::kUnpack2X16Snorm:
+        case builtin::Function::kUnpack2X16Snorm:
             return GLSLstd450UnpackSnorm2x16;
-        case BuiltinType::kUnpack2X16Unorm:
+        case builtin::Function::kUnpack2X16Unorm:
             return GLSLstd450UnpackUnorm2x16;
-        case BuiltinType::kUnpack2X16Float:
+        case builtin::Function::kUnpack2X16Float:
             return GLSLstd450UnpackHalf2x16;
         default:
             break;
@@ -2363,21 +2361,21 @@
     };
 
     switch (builtin->Type()) {
-        case BuiltinType::kAny:
+        case builtin::Function::kAny:
             if (builtin->Parameters()[0]->Type()->Is<type::Bool>()) {
                 // any(v: bool) just resolves to v.
                 return get_arg_as_value_id(0);
             }
             op = spv::Op::OpAny;
             break;
-        case BuiltinType::kAll:
+        case builtin::Function::kAll:
             if (builtin->Parameters()[0]->Type()->Is<type::Bool>()) {
                 // all(v: bool) just resolves to v.
                 return get_arg_as_value_id(0);
             }
             op = spv::Op::OpAll;
             break;
-        case BuiltinType::kArrayLength: {
+        case builtin::Function::kArrayLength: {
             auto* address_of = call->Arguments()[0]->Declaration()->As<ast::UnaryOpExpression>();
             if (!address_of || address_of->op != ast::UnaryOp::kAddressOf) {
                 error_ = "arrayLength() expected pointer to member access, got " +
@@ -2414,10 +2412,10 @@
             }
             return result_id;
         }
-        case BuiltinType::kCountOneBits:
+        case builtin::Function::kCountOneBits:
             op = spv::Op::OpBitCount;
             break;
-        case BuiltinType::kDot: {
+        case builtin::Function::kDot: {
             op = spv::Op::OpDot;
             auto* vec_ty = builtin->Parameters()[0]->Type()->As<type::Vector>();
             if (vec_ty->type()->is_integer_scalar()) {
@@ -2458,42 +2456,42 @@
             }
             break;
         }
-        case BuiltinType::kDpdx:
+        case builtin::Function::kDpdx:
             op = spv::Op::OpDPdx;
             break;
-        case BuiltinType::kDpdxCoarse:
+        case builtin::Function::kDpdxCoarse:
             op = spv::Op::OpDPdxCoarse;
             break;
-        case BuiltinType::kDpdxFine:
+        case builtin::Function::kDpdxFine:
             op = spv::Op::OpDPdxFine;
             break;
-        case BuiltinType::kDpdy:
+        case builtin::Function::kDpdy:
             op = spv::Op::OpDPdy;
             break;
-        case BuiltinType::kDpdyCoarse:
+        case builtin::Function::kDpdyCoarse:
             op = spv::Op::OpDPdyCoarse;
             break;
-        case BuiltinType::kDpdyFine:
+        case builtin::Function::kDpdyFine:
             op = spv::Op::OpDPdyFine;
             break;
-        case BuiltinType::kExtractBits:
+        case builtin::Function::kExtractBits:
             op = builtin->Parameters()[0]->Type()->is_unsigned_integer_scalar_or_vector()
                      ? spv::Op::OpBitFieldUExtract
                      : spv::Op::OpBitFieldSExtract;
             break;
-        case BuiltinType::kFwidth:
+        case builtin::Function::kFwidth:
             op = spv::Op::OpFwidth;
             break;
-        case BuiltinType::kFwidthCoarse:
+        case builtin::Function::kFwidthCoarse:
             op = spv::Op::OpFwidthCoarse;
             break;
-        case BuiltinType::kFwidthFine:
+        case builtin::Function::kFwidthFine:
             op = spv::Op::OpFwidthFine;
             break;
-        case BuiltinType::kInsertBits:
+        case builtin::Function::kInsertBits:
             op = spv::Op::OpBitFieldInsert;
             break;
-        case BuiltinType::kMix: {
+        case builtin::Function::kMix: {
             auto std450 = Operand(GetGLSLstd450Import());
 
             auto a_id = get_arg_as_value_id(0);
@@ -2520,13 +2518,13 @@
             }
             return result_id;
         }
-        case BuiltinType::kQuantizeToF16:
+        case builtin::Function::kQuantizeToF16:
             op = spv::Op::OpQuantizeToF16;
             break;
-        case BuiltinType::kReverseBits:
+        case builtin::Function::kReverseBits:
             op = spv::Op::OpBitReverse;
             break;
-        case BuiltinType::kSelect: {
+        case builtin::Function::kSelect: {
             // Note: Argument order is different in WGSL and SPIR-V
             auto cond_id = get_arg_as_value_id(2);
             auto true_id = get_arg_as_value_id(1);
@@ -2558,10 +2556,10 @@
             }
             return result_id;
         }
-        case BuiltinType::kTranspose:
+        case builtin::Function::kTranspose:
             op = spv::Op::OpTranspose;
             break;
-        case BuiltinType::kAbs:
+        case builtin::Function::kAbs:
             if (builtin->ReturnType()->is_unsigned_integer_scalar_or_vector()) {
                 // abs() only operates on *signed* integers.
                 // This is a no-op for unsigned integers.
@@ -2573,7 +2571,7 @@
                 glsl_std450(GLSLstd450SAbs);
             }
             break;
-        case BuiltinType::kDot4I8Packed: {
+        case builtin::Function::kDot4I8Packed: {
             auto first_param_id = get_arg_as_value_id(0);
             auto second_param_id = get_arg_as_value_id(1);
             if (!push_function_inst(spv::Op::OpSDotKHR,
@@ -2585,7 +2583,7 @@
             }
             return result_id;
         }
-        case BuiltinType::kDot4U8Packed: {
+        case builtin::Function::kDot4U8Packed: {
             auto first_param_id = get_arg_as_value_id(0);
             auto second_param_id = get_arg_as_value_id(1);
             if (!push_function_inst(spv::Op::OpUDotKHR,
@@ -2788,7 +2786,7 @@
     };
 
     switch (builtin->Type()) {
-        case BuiltinType::kTextureDimensions: {
+        case builtin::Function::kTextureDimensions: {
             // Number of returned elements from OpImageQuerySize[Lod] may not match
             // those of textureDimensions().
             // This might be due to an extra vector scalar describing the number of
@@ -2833,7 +2831,7 @@
             }
             break;
         }
-        case BuiltinType::kTextureNumLayers: {
+        case builtin::Function::kTextureNumLayers: {
             uint32_t spirv_dims = 0;
             switch (texture_type->dim()) {
                 default:
@@ -2863,19 +2861,19 @@
             }
             break;
         }
-        case BuiltinType::kTextureNumLevels: {
+        case builtin::Function::kTextureNumLevels: {
             op = spv::Op::OpImageQueryLevels;
             append_result_type_and_id_to_spirv_params();
             spirv_params.emplace_back(gen_arg(Usage::kTexture));
             break;
         }
-        case BuiltinType::kTextureNumSamples: {
+        case builtin::Function::kTextureNumSamples: {
             op = spv::Op::OpImageQuerySamples;
             append_result_type_and_id_to_spirv_params();
             spirv_params.emplace_back(gen_arg(Usage::kTexture));
             break;
         }
-        case BuiltinType::kTextureLoad: {
+        case builtin::Function::kTextureLoad: {
             op = texture_type->Is<type::StorageTexture>() ? spv::Op::OpImageRead
                                                           : spv::Op::OpImageFetch;
             append_result_type_and_id_to_spirv_params_for_read();
@@ -2895,7 +2893,7 @@
 
             break;
         }
-        case BuiltinType::kTextureStore: {
+        case builtin::Function::kTextureStore: {
             op = spv::Op::OpImageWrite;
             spirv_params.emplace_back(gen_arg(Usage::kTexture));
             if (!append_coords_to_spirv_params()) {
@@ -2904,7 +2902,7 @@
             spirv_params.emplace_back(gen_arg(Usage::kValue));
             break;
         }
-        case BuiltinType::kTextureGather: {
+        case builtin::Function::kTextureGather: {
             op = spv::Op::OpImageGather;
             append_result_type_and_id_to_spirv_params();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2918,7 +2916,7 @@
             }
             break;
         }
-        case BuiltinType::kTextureGatherCompare: {
+        case builtin::Function::kTextureGatherCompare: {
             op = spv::Op::OpImageDrefGather;
             append_result_type_and_id_to_spirv_params();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2927,7 +2925,7 @@
             spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
             break;
         }
-        case BuiltinType::kTextureSample: {
+        case builtin::Function::kTextureSample: {
             op = spv::Op::OpImageSampleImplicitLod;
             append_result_type_and_id_to_spirv_params_for_read();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2935,7 +2933,7 @@
             }
             break;
         }
-        case BuiltinType::kTextureSampleBias: {
+        case builtin::Function::kTextureSampleBias: {
             op = spv::Op::OpImageSampleImplicitLod;
             append_result_type_and_id_to_spirv_params_for_read();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2945,7 +2943,7 @@
                 ImageOperand{SpvImageOperandsBiasMask, gen_arg(Usage::kBias)});
             break;
         }
-        case BuiltinType::kTextureSampleLevel: {
+        case builtin::Function::kTextureSampleLevel: {
             op = spv::Op::OpImageSampleExplicitLod;
             append_result_type_and_id_to_spirv_params_for_read();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2970,7 +2968,7 @@
             image_operands.emplace_back(ImageOperand{SpvImageOperandsLodMask, level});
             break;
         }
-        case BuiltinType::kTextureSampleGrad: {
+        case builtin::Function::kTextureSampleGrad: {
             op = spv::Op::OpImageSampleExplicitLod;
             append_result_type_and_id_to_spirv_params_for_read();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2982,7 +2980,7 @@
                 ImageOperand{SpvImageOperandsGradMask, gen_arg(Usage::kDdy)});
             break;
         }
-        case BuiltinType::kTextureSampleCompare: {
+        case builtin::Function::kTextureSampleCompare: {
             op = spv::Op::OpImageSampleDrefImplicitLod;
             append_result_type_and_id_to_spirv_params();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -2991,7 +2989,7 @@
             spirv_params.emplace_back(gen_arg(Usage::kDepthRef));
             break;
         }
-        case BuiltinType::kTextureSampleCompareLevel: {
+        case builtin::Function::kTextureSampleCompareLevel: {
             op = spv::Op::OpImageSampleDrefExplicitLod;
             append_result_type_and_id_to_spirv_params();
             if (!append_image_and_coords_to_spirv_params()) {
@@ -3046,19 +3044,19 @@
 
     // TODO(crbug.com/tint/661): Combine sequential barriers to a single
     // instruction.
-    if (builtin->Type() == sem::BuiltinType::kWorkgroupBarrier) {
+    if (builtin->Type() == builtin::Function::kWorkgroupBarrier) {
         execution = static_cast<uint32_t>(spv::Scope::Workgroup);
         memory = static_cast<uint32_t>(spv::Scope::Workgroup);
         semantics = static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
                     static_cast<uint32_t>(spv::MemorySemanticsMask::WorkgroupMemory);
-    } else if (builtin->Type() == sem::BuiltinType::kStorageBarrier) {
+    } else if (builtin->Type() == builtin::Function::kStorageBarrier) {
         execution = static_cast<uint32_t>(spv::Scope::Workgroup);
         memory = static_cast<uint32_t>(spv::Scope::Workgroup);
         semantics = static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease) |
                     static_cast<uint32_t>(spv::MemorySemanticsMask::UniformMemory);
     } else {
         error_ = "unexpected barrier builtin type ";
-        error_ += sem::str(builtin->Type());
+        error_ += builtin::str(builtin->Type());
         return false;
     }
 
@@ -3128,7 +3126,7 @@
     Operand semantics = Operand(semantics_id);
 
     switch (builtin->Type()) {
-        case sem::BuiltinType::kAtomicLoad:
+        case builtin::Function::kAtomicLoad:
             return push_function_inst(spv::Op::OpAtomicLoad, {
                                                                  result_type,
                                                                  result_id,
@@ -3136,14 +3134,14 @@
                                                                  memory,
                                                                  semantics,
                                                              });
-        case sem::BuiltinType::kAtomicStore:
+        case builtin::Function::kAtomicStore:
             return push_function_inst(spv::Op::OpAtomicStore, {
                                                                   pointer,
                                                                   memory,
                                                                   semantics,
                                                                   value,
                                                               });
-        case sem::BuiltinType::kAtomicAdd:
+        case builtin::Function::kAtomicAdd:
             return push_function_inst(spv::Op::OpAtomicIAdd, {
                                                                  result_type,
                                                                  result_id,
@@ -3152,7 +3150,7 @@
                                                                  semantics,
                                                                  value,
                                                              });
-        case sem::BuiltinType::kAtomicSub:
+        case builtin::Function::kAtomicSub:
             return push_function_inst(spv::Op::OpAtomicISub, {
                                                                  result_type,
                                                                  result_id,
@@ -3161,7 +3159,7 @@
                                                                  semantics,
                                                                  value,
                                                              });
-        case sem::BuiltinType::kAtomicMax:
+        case builtin::Function::kAtomicMax:
             return push_function_inst(
                 is_value_signed() ? spv::Op::OpAtomicSMax : spv::Op::OpAtomicUMax, {
                                                                                        result_type,
@@ -3171,7 +3169,7 @@
                                                                                        semantics,
                                                                                        value,
                                                                                    });
-        case sem::BuiltinType::kAtomicMin:
+        case builtin::Function::kAtomicMin:
             return push_function_inst(
                 is_value_signed() ? spv::Op::OpAtomicSMin : spv::Op::OpAtomicUMin, {
                                                                                        result_type,
@@ -3181,7 +3179,7 @@
                                                                                        semantics,
                                                                                        value,
                                                                                    });
-        case sem::BuiltinType::kAtomicAnd:
+        case builtin::Function::kAtomicAnd:
             return push_function_inst(spv::Op::OpAtomicAnd, {
                                                                 result_type,
                                                                 result_id,
@@ -3190,7 +3188,7 @@
                                                                 semantics,
                                                                 value,
                                                             });
-        case sem::BuiltinType::kAtomicOr:
+        case builtin::Function::kAtomicOr:
             return push_function_inst(spv::Op::OpAtomicOr, {
                                                                result_type,
                                                                result_id,
@@ -3199,7 +3197,7 @@
                                                                semantics,
                                                                value,
                                                            });
-        case sem::BuiltinType::kAtomicXor:
+        case builtin::Function::kAtomicXor:
             return push_function_inst(spv::Op::OpAtomicXor, {
                                                                 result_type,
                                                                 result_id,
@@ -3208,7 +3206,7 @@
                                                                 semantics,
                                                                 value,
                                                             });
-        case sem::BuiltinType::kAtomicExchange:
+        case builtin::Function::kAtomicExchange:
             return push_function_inst(spv::Op::OpAtomicExchange, {
                                                                      result_type,
                                                                      result_id,
@@ -3217,7 +3215,7 @@
                                                                      semantics,
                                                                      value,
                                                                  });
-        case sem::BuiltinType::kAtomicCompareExchangeWeak: {
+        case builtin::Function::kAtomicCompareExchangeWeak: {
             auto comparator = GenerateExpression(call->Arguments()[1]);
             if (comparator == 0) {
                 return false;
diff --git a/src/tint/writer/spirv/builder_builtin_test.cc b/src/tint/writer/spirv/builder_builtin_test.cc
index 5db867a..280b878 100644
--- a/src/tint/writer/spirv/builder_builtin_test.cc
+++ b/src/tint/writer/spirv/builder_builtin_test.cc
@@ -4154,10 +4154,7 @@
 namespace DP4A_builtin_tests {
 
 TEST_F(BuiltinBuilderTest, Call_Dot4I8Packed) {
-    auto* ext =
-        create<ast::Enable>(Source{Source::Range{Source::Location{10, 2}, Source::Location{10, 5}}},
-                            builtin::Extension::kChromiumExperimentalDp4A);
-    AST().AddEnable(ext);
+    Enable(builtin::Extension::kChromiumExperimentalDp4A);
 
     auto* val1 = Var("val1", ty.u32());
     auto* val2 = Var("val2", ty.u32());
@@ -4194,10 +4191,7 @@
 }
 
 TEST_F(BuiltinBuilderTest, Call_Dot4U8Packed) {
-    auto* ext =
-        create<ast::Enable>(Source{Source::Range{Source::Location{10, 2}, Source::Location{10, 5}}},
-                            builtin::Extension::kChromiumExperimentalDp4A);
-    AST().AddEnable(ext);
+    Enable(builtin::Extension::kChromiumExperimentalDp4A);
 
     auto* val1 = Var("val1", ty.u32());
     auto* val2 = Var("val2", ty.u32());
diff --git a/src/tint/writer/spirv/builder_builtin_texture_test.cc b/src/tint/writer/spirv/builder_builtin_texture_test.cc
index 766b78b..4697582 100644
--- a/src/tint/writer/spirv/builder_builtin_texture_test.cc
+++ b/src/tint/writer/spirv/builder_builtin_texture_test.cc
@@ -1132,6 +1132,29 @@
                     R"(
 OpCapability ImageQuery
 )"};
+        case ValidTextureOverload::kNumLayersCubeArray:
+            return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 0
+%11 = OpTypeVector %9 3
+%13 = OpTypeInt 32 1
+%14 = OpConstant %13 0
+)",
+                    R"(
+%12 = OpLoad %3 %1
+%10 = OpImageQuerySizeLod %11 %12 %14
+%8 = OpCompositeExtract %9 %10 2
+)",
+                    R"(
+OpCapability SampledCubeArray
+OpCapability ImageQuery
+)"};
         case ValidTextureOverload::kNumLayersDepth2dArray:
             return {R"(
 %4 = OpTypeFloat 32
@@ -1154,6 +1177,29 @@
                     R"(
 OpCapability ImageQuery
 )"};
+        case ValidTextureOverload::kNumLayersDepthCubeArray:
+            return {R"(
+%4 = OpTypeFloat 32
+%3 = OpTypeImage %4 Cube 0 1 0 1 Unknown
+%2 = OpTypePointer UniformConstant %3
+%1 = OpVariable %2 UniformConstant
+%7 = OpTypeSampler
+%6 = OpTypePointer UniformConstant %7
+%5 = OpVariable %6 UniformConstant
+%9 = OpTypeInt 32 0
+%11 = OpTypeVector %9 3
+%13 = OpTypeInt 32 1
+%14 = OpConstant %13 0
+)",
+                    R"(
+%12 = OpLoad %3 %1
+%10 = OpImageQuerySizeLod %11 %12 %14
+%8 = OpCompositeExtract %9 %10 2
+)",
+                    R"(
+OpCapability SampledCubeArray
+OpCapability ImageQuery
+)"};
         case ValidTextureOverload::kNumLayersStorageWO2dArray:
             return {R"(
 %4 = OpTypeFloat 32
diff --git a/src/tint/writer/spirv/generator.h b/src/tint/writer/spirv/generator.h
index 8b34032..f6d2268 100644
--- a/src/tint/writer/spirv/generator.h
+++ b/src/tint/writer/spirv/generator.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "src/tint/reflection.h"
+#include "src/tint/writer/external_texture_options.h"
 #include "src/tint/writer/writer.h"
 
 // Forward declarations
@@ -45,8 +46,11 @@
     /// Set to `true` to disable workgroup memory zero initialization
     bool disable_workgroup_init = false;
 
-    /// Set to 'true' to generates binding mappings for external textures
-    bool generate_external_texture_bindings = false;
+    /// Set to `true` to clamp frag depth
+    bool clamp_frag_depth = false;
+
+    /// Options used in the binding mappings for external textures
+    ExternalTextureOptions external_texture_options = {};
 
     /// Set to `true` to initialize workgroup memory with OpConstantNull when
     /// VK_KHR_zero_initialize_workgroup_memory is enabled.
@@ -56,7 +60,7 @@
     TINT_REFLECT(disable_robustness,
                  emit_vertex_point_size,
                  disable_workgroup_init,
-                 generate_external_texture_bindings,
+                 external_texture_options,
                  use_zero_initialize_workgroup_memory_extension);
 };
 
diff --git a/src/tint/writer/spirv/generator_impl.cc b/src/tint/writer/spirv/generator_impl.cc
index 72a3629..1986f83 100644
--- a/src/tint/writer/spirv/generator_impl.cc
+++ b/src/tint/writer/spirv/generator_impl.cc
@@ -21,6 +21,7 @@
 #include "src/tint/transform/add_empty_entry_point.h"
 #include "src/tint/transform/builtin_polyfill.h"
 #include "src/tint/transform/canonicalize_entry_point_io.h"
+#include "src/tint/transform/clamp_frag_depth.h"
 #include "src/tint/transform/demote_to_helper.h"
 #include "src/tint/transform/direct_variable_access.h"
 #include "src/tint/transform/disable_uniformity_analysis.h"
@@ -28,6 +29,7 @@
 #include "src/tint/transform/for_loop_to_loop.h"
 #include "src/tint/transform/manager.h"
 #include "src/tint/transform/merge_return.h"
+#include "src/tint/transform/multiplanar_external_texture.h"
 #include "src/tint/transform/preserve_padding.h"
 #include "src/tint/transform/promote_side_effects_to_decl.h"
 #include "src/tint/transform/remove_phonies.h"
@@ -41,7 +43,6 @@
 #include "src/tint/transform/vectorize_scalar_matrix_initializers.h"
 #include "src/tint/transform/while_to_loop.h"
 #include "src/tint/transform/zero_init_workgroup_memory.h"
-#include "src/tint/writer/generate_external_texture_bindings.h"
 
 namespace tint::writer::spirv {
 
@@ -49,6 +50,10 @@
     transform::Manager manager;
     transform::DataMap data;
 
+    if (options.clamp_frag_depth) {
+        manager.Add<tint::transform::ClampFragDepth>();
+    }
+
     manager.Add<transform::DisableUniformityAnalysis>();
 
     // ExpandCompoundAssignment must come before BuiltinPolyfill
@@ -73,10 +78,10 @@
         manager.Add<transform::Robustness>();
     }
 
-    if (options.generate_external_texture_bindings) {
+    if (!options.external_texture_options.bindings_map.empty()) {
         // Note: it is more efficient for MultiplanarExternalTexture to come after Robustness
-        auto new_bindings_map = GenerateExternalTextureBindings(in);
-        data.Add<transform::MultiplanarExternalTexture::NewBindingPoints>(new_bindings_map);
+        data.Add<transform::MultiplanarExternalTexture::NewBindingPoints>(
+            options.external_texture_options.bindings_map);
         manager.Add<transform::MultiplanarExternalTexture>();
     }
 
@@ -89,6 +94,7 @@
         polyfills.bgra8unorm = true;
         polyfills.bitshift_modulo = true;
         polyfills.clamp_int = true;
+        polyfills.conv_f32_to_iu32 = true;
         polyfills.count_leading_zeros = true;
         polyfills.count_trailing_zeros = true;
         polyfills.extract_bits = transform::BuiltinPolyfill::Level::kClampParameters;
diff --git a/src/tint/writer/syntax_tree/generator.cc b/src/tint/writer/syntax_tree/generator.cc
new file mode 100644
index 0000000..6919d24
--- /dev/null
+++ b/src/tint/writer/syntax_tree/generator.cc
@@ -0,0 +1,36 @@
+// Copyright 2023 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/tint/writer/syntax_tree/generator.h"
+#include "src/tint/writer/syntax_tree/generator_impl.h"
+
+namespace tint::writer::syntax_tree {
+
+Result::Result() = default;
+Result::~Result() = default;
+Result::Result(const Result&) = default;
+
+Result Generate(const Program* program, const Options&) {
+    Result result;
+
+    // Generate the AST dump.
+    auto impl = std::make_unique<GeneratorImpl>(program);
+    result.success = impl->Generate();
+    result.error = impl->error();
+    result.ast = impl->result();
+
+    return result;
+}
+
+}  // namespace tint::writer::syntax_tree
diff --git a/src/tint/writer/syntax_tree/generator.h b/src/tint/writer/syntax_tree/generator.h
new file mode 100644
index 0000000..ce81c3f
--- /dev/null
+++ b/src/tint/writer/syntax_tree/generator.h
@@ -0,0 +1,65 @@
+// Copyright 2023 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_TINT_WRITER_SYNTAX_TREE_GENERATOR_H_
+#define SRC_TINT_WRITER_SYNTAX_TREE_GENERATOR_H_
+
+#include <memory>
+#include <string>
+
+#include "src/tint/writer/text.h"
+
+// Forward declarations
+namespace tint {
+class Program;
+}  // namespace tint
+
+namespace tint::writer::syntax_tree {
+
+class GeneratorImpl;
+
+/// Configuration options used for generating AST.
+struct Options {};
+
+/// The result produced when generating AST.
+struct Result {
+    /// Constructor
+    Result();
+
+    /// Destructor
+    ~Result();
+
+    /// Copy constructor
+    Result(const Result&);
+
+    /// True if generation was successful.
+    bool success = false;
+
+    /// The errors generated during code generation, if any.
+    std::string error;
+
+    /// The generated AST.
+    std::string ast = "";
+};
+
+/// Generate an AST dump for a program, according to a set of configuration options.
+/// The result will contain the AST, as well as success status and diagnostic information.
+/// @param program the program to dump
+/// @param options the configuration options to use when dumping
+/// @returns the resulting AST dump and supplementary information
+Result Generate(const Program* program, const Options& options);
+
+}  // namespace tint::writer::syntax_tree
+
+#endif  // SRC_TINT_WRITER_SYNTAX_TREE_GENERATOR_H_
diff --git a/src/tint/writer/syntax_tree/generator_impl.cc b/src/tint/writer/syntax_tree/generator_impl.cc
new file mode 100644
index 0000000..02e6ff7
--- /dev/null
+++ b/src/tint/writer/syntax_tree/generator_impl.cc
@@ -0,0 +1,1358 @@
+// Copyright 2023 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/tint/writer/syntax_tree/generator_impl.h"
+
+#include <algorithm>
+
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/bool_literal_expression.h"
+#include "src/tint/ast/call_statement.h"
+#include "src/tint/ast/float_literal_expression.h"
+#include "src/tint/ast/id_attribute.h"
+#include "src/tint/ast/internal_attribute.h"
+#include "src/tint/ast/interpolate_attribute.h"
+#include "src/tint/ast/invariant_attribute.h"
+#include "src/tint/ast/module.h"
+#include "src/tint/ast/stage_attribute.h"
+#include "src/tint/ast/stride_attribute.h"
+#include "src/tint/ast/struct_member_align_attribute.h"
+#include "src/tint/ast/struct_member_offset_attribute.h"
+#include "src/tint/ast/struct_member_size_attribute.h"
+#include "src/tint/ast/variable_decl_statement.h"
+#include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/switch_statement.h"
+#include "src/tint/switch.h"
+#include "src/tint/utils/math.h"
+#include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/writer/float_to_string.h"
+
+namespace tint::writer::syntax_tree {
+
+GeneratorImpl::GeneratorImpl(const Program* program) : TextGenerator(program) {}
+
+GeneratorImpl::~GeneratorImpl() = default;
+
+bool GeneratorImpl::Generate() {
+    // Generate global declarations in the order they appear in the module.
+    for (auto* decl : program_->AST().GlobalDeclarations()) {
+        if (!Switch(
+                decl,  //
+                [&](const ast::DiagnosticDirective* dd) {
+                    return EmitDiagnosticControl(dd->control);
+                },
+                [&](const ast::Enable* e) { return EmitEnable(e); },
+                [&](const ast::TypeDecl* td) { return EmitTypeDecl(td); },
+                [&](const ast::Function* func) { return EmitFunction(func); },
+                [&](const ast::Variable* var) { return EmitVariable(var); },
+                [&](const ast::ConstAssert* ca) { return EmitConstAssert(ca); },
+                [&](Default) {
+                    TINT_UNREACHABLE(Writer, diagnostics_);
+                    return false;
+                })) {
+            return false;
+        }
+        if (decl != program_->AST().GlobalDeclarations().Back()) {
+            line();
+        }
+    }
+
+    return true;
+}
+
+bool GeneratorImpl::EmitDiagnosticControl(const ast::DiagnosticControl& diagnostic) {
+    line() << "DiagnosticControl [severity: " << diagnostic.severity
+           << ", rule: " << program_->Symbols().NameFor(diagnostic.rule_name->symbol) << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitEnable(const ast::Enable* enable) {
+    auto l = line();
+    l << "Enable [";
+    for (auto* ext : enable->extensions) {
+        if (ext != enable->extensions.Front()) {
+            l << ", ";
+        }
+        l << ext->name;
+    }
+    l << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitTypeDecl(const ast::TypeDecl* ty) {
+    return Switch(
+        ty,
+        [&](const ast::Alias* alias) {  //
+            line() << "Alias [";
+            {
+                ScopedIndent ai(this);
+
+                line() << "name: " << program_->Symbols().NameFor(alias->name->symbol);
+                line() << "expr: ";
+                {
+                    ScopedIndent ex(this);
+                    if (!EmitExpression(alias->type)) {
+                        return false;
+                    }
+                }
+            }
+            line() << "]";
+            return true;
+        },
+        [&](const ast::Struct* str) {  //
+            return EmitStructType(str);
+        },
+        [&](Default) {  //
+            diagnostics_.add_error(diag::System::Writer,
+                                   "unknown declared type: " + std::string(ty->TypeInfo().name));
+            return false;
+        });
+}
+
+bool GeneratorImpl::EmitExpression(const ast::Expression* expr) {
+    return Switch(
+        expr,
+        [&](const ast::IndexAccessorExpression* a) {  //
+            return EmitIndexAccessor(a);
+        },
+        [&](const ast::BinaryExpression* b) {  //
+            return EmitBinary(b);
+        },
+        [&](const ast::BitcastExpression* b) {  //
+            return EmitBitcast(b);
+        },
+        [&](const ast::CallExpression* c) {  //
+            return EmitCall(c);
+        },
+        [&](const ast::IdentifierExpression* i) {  //
+            return EmitIdentifier(i);
+        },
+        [&](const ast::LiteralExpression* l) {  //
+            return EmitLiteral(l);
+        },
+        [&](const ast::MemberAccessorExpression* m) {  //
+            return EmitMemberAccessor(m);
+        },
+        [&](const ast::PhonyExpression*) {  //
+            line() << "[PhonyExpression]";
+            return true;
+        },
+        [&](const ast::UnaryOpExpression* u) {  //
+            return EmitUnaryOp(u);
+        },
+        [&](Default) {
+            diagnostics_.add_error(diag::System::Writer, "unknown expression type");
+            return false;
+        });
+}
+
+bool GeneratorImpl::EmitIndexAccessor(const ast::IndexAccessorExpression* expr) {
+    line() << "IndexAccessorExpression [";
+    {
+        ScopedIndent iae(this);
+        line() << "object: ";
+        {
+            ScopedIndent obj(this);
+            if (!EmitExpression(expr->object)) {
+                return false;
+            }
+        }
+
+        line() << "index: ";
+        {
+            ScopedIndent idx(this);
+            if (!EmitExpression(expr->index)) {
+                return false;
+            }
+        }
+    }
+    line() << "]";
+
+    return true;
+}
+
+bool GeneratorImpl::EmitMemberAccessor(const ast::MemberAccessorExpression* expr) {
+    line() << "MemberAccessorExpression [";
+    {
+        ScopedIndent mae(this);
+
+        line() << "object: ";
+        {
+            ScopedIndent obj(this);
+            if (!EmitExpression(expr->object)) {
+                return false;
+            }
+        }
+        line() << "member: " << program_->Symbols().NameFor(expr->member->symbol);
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitBitcast(const ast::BitcastExpression* expr) {
+    line() << "BitcastExpression [";
+    {
+        ScopedIndent bc(this);
+        {
+            line() << "type: ";
+            ScopedIndent ty(this);
+            if (!EmitExpression(expr->type)) {
+                return false;
+            }
+        }
+        {
+            line() << "expr: ";
+            ScopedIndent exp(this);
+            if (!EmitExpression(expr->expr)) {
+                return false;
+            }
+        }
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitCall(const ast::CallExpression* expr) {
+    line() << "Call [";
+    {
+        ScopedIndent cl(this);
+
+        line() << "target: [";
+        {
+            ScopedIndent tgt(this);
+            if (!EmitExpression(expr->target)) {
+                return false;
+            }
+        }
+        line() << "]";
+
+        if (!expr->args.IsEmpty()) {
+            line() << "args: [";
+            {
+                ScopedIndent args(this);
+                for (auto* arg : expr->args) {
+                    line() << "arg: [";
+                    {
+                        ScopedIndent arg_val(this);
+                        if (!EmitExpression(arg)) {
+                            return false;
+                        }
+                    }
+                    line() << "]";
+                }
+            }
+            line() << "]";
+        }
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitLiteral(const ast::LiteralExpression* lit) {
+    bool ret = false;
+    line() << "LiteralExpression [";
+    {
+        ScopedIndent le(this);
+        ret = Switch(
+            lit,
+            [&](const ast::BoolLiteralExpression* l) {  //
+                line() << (l->value ? "true" : "false");
+                return true;
+            },
+            [&](const ast::FloatLiteralExpression* l) {  //
+                // f16 literals are also emitted as float value with suffix "h".
+                // Note that all normal and subnormal f16 values are normal f32 values, and since
+                // NaN and Inf are not allowed to be spelled in literal, it should be fine to emit
+                // f16 literals in this way.
+                if (l->suffix == ast::FloatLiteralExpression::Suffix::kNone) {
+                    line() << DoubleToBitPreservingString(l->value);
+                } else {
+                    line() << FloatToBitPreservingString(static_cast<float>(l->value)) << l->suffix;
+                }
+                return true;
+            },
+            [&](const ast::IntLiteralExpression* l) {  //
+                line() << l->value << l->suffix;
+                return true;
+            },
+            [&](Default) {  //
+                diagnostics_.add_error(diag::System::Writer, "unknown literal type");
+                return false;
+            });
+    }
+    line() << "]";
+    return ret;
+}
+
+bool GeneratorImpl::EmitIdentifier(const ast::IdentifierExpression* expr) {
+    bool ret = false;
+    line() << "IdentifierExpression [";
+    {
+        ScopedIndent ie(this);
+        ret = EmitIdentifier(expr->identifier);
+    }
+    line() << "]";
+    return ret;
+}
+
+bool GeneratorImpl::EmitIdentifier(const ast::Identifier* ident) {
+    line() << "Identifier [";
+    {
+        ScopedIndent id(this);
+        if (auto* tmpl_ident = ident->As<ast::TemplatedIdentifier>()) {
+            line() << "Templated [";
+            {
+                ScopedIndent tmpl(this);
+                if (!tmpl_ident->attributes.IsEmpty()) {
+                    line() << "attrs: [";
+                    {
+                        ScopedIndent attrs(this);
+                        EmitAttributes(tmpl_ident->attributes);
+                    }
+                    line() << "]";
+                }
+                line() << "name: " << program_->Symbols().NameFor(ident->symbol);
+                if (!tmpl_ident->arguments.IsEmpty()) {
+                    line() << "args: [";
+                    {
+                        ScopedIndent args(this);
+                        for (auto* expr : tmpl_ident->arguments) {
+                            if (!EmitExpression(expr)) {
+                                return false;
+                            }
+                        }
+                    }
+                    line() << "]";
+                }
+            }
+            line() << "]";
+        } else {
+            line() << program_->Symbols().NameFor(ident->symbol);
+        }
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitFunction(const ast::Function* func) {
+    line() << "Function [";
+    {
+        ScopedIndent funct(this);
+
+        if (func->attributes.Length()) {
+            line() << "attrs: [";
+            {
+                ScopedIndent attrs(this);
+                if (!EmitAttributes(func->attributes)) {
+                    return false;
+                }
+            }
+            line() << "]";
+        }
+        line() << "name: " << program_->Symbols().NameFor(func->name->symbol);
+
+        if (!func->params.IsEmpty()) {
+            line() << "params: [";
+            {
+                ScopedIndent args(this);
+                for (auto* v : func->params) {
+                    line() << "param: [";
+                    {
+                        ScopedIndent param(this);
+                        line() << "name: " << program_->Symbols().NameFor(v->name->symbol);
+                        if (!v->attributes.IsEmpty()) {
+                            line() << "attrs: [";
+                            {
+                                ScopedIndent attrs(this);
+                                if (!EmitAttributes(v->attributes)) {
+                                    return false;
+                                }
+                            }
+                            line() << "]";
+                        }
+                        line() << "type: [";
+                        {
+                            ScopedIndent ty(this);
+                            if (!EmitExpression(v->type)) {
+                                return false;
+                            }
+                        }
+                        line() << "]";
+                    }
+                    line() << "]";
+                }
+            }
+            line() << "]";
+        }
+
+        line() << "return: [";
+        {
+            ScopedIndent ret(this);
+
+            if (func->return_type || !func->return_type_attributes.IsEmpty()) {
+                if (!func->return_type_attributes.IsEmpty()) {
+                    line() << "attrs: [";
+                    {
+                        ScopedIndent attrs(this);
+                        if (!EmitAttributes(func->return_type_attributes)) {
+                            return false;
+                        }
+                    }
+                    line() << "]";
+                }
+
+                line() << "type: [";
+                {
+                    ScopedIndent ty(this);
+                    if (!EmitExpression(func->return_type)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+            } else {
+                line() << "void";
+            }
+        }
+        line() << "]";
+        line() << "body: [";
+        {
+            ScopedIndent bdy(this);
+            if (func->body) {
+                if (!EmitBlockHeader(func->body)) {
+                    return false;
+                }
+                if (!EmitStatementsWithIndent(func->body->statements)) {
+                    return false;
+                }
+            }
+        }
+        line() << "]";
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitImageFormat(const builtin::TexelFormat fmt) {
+    line() << "builtin::TexelFormat [" << fmt << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitStructType(const ast::Struct* str) {
+    line() << "Struct [";
+    {
+        ScopedIndent strct(this);
+
+        if (str->attributes.Length()) {
+            line() << "attrs: [";
+            {
+                ScopedIndent attrs(this);
+                if (!EmitAttributes(str->attributes)) {
+                    return false;
+                }
+            }
+            line() << "]";
+        }
+        line() << "name: " << program_->Symbols().NameFor(str->name->symbol);
+        line() << "members: [";
+        {
+            ScopedIndent membs(this);
+
+            for (auto* mem : str->members) {
+                line() << "StructMember[";
+                {
+                    ScopedIndent m(this);
+                    if (!mem->attributes.IsEmpty()) {
+                        line() << "attrs: [";
+                        {
+                            ScopedIndent attrs(this);
+                            if (!EmitAttributes(mem->attributes)) {
+                                return false;
+                            }
+                        }
+                        line() << "]";
+                    }
+
+                    line() << "name: " << program_->Symbols().NameFor(mem->name->symbol);
+                    line() << "type: [";
+                    {
+                        ScopedIndent ty(this);
+                        if (!EmitExpression(mem->type)) {
+                            return false;
+                        }
+                    }
+                    line() << "]";
+                }
+            }
+            line() << "]";
+        }
+        line() << "]";
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitVariable(const ast::Variable* v) {
+    line() << "Variable [";
+    {
+        ScopedIndent variable(this);
+        if (!v->attributes.IsEmpty()) {
+            line() << "attrs: [";
+            {
+                ScopedIndent attr(this);
+                if (!EmitAttributes(v->attributes)) {
+                    return false;
+                }
+            }
+            line() << "]";
+        }
+
+        bool ok = Switch(
+            v,  //
+            [&](const ast::Var* var) {
+                if (var->declared_address_space || var->declared_access) {
+                    line() << "Var [";
+                    {
+                        ScopedIndent vr(this);
+                        line() << "address_space: [";
+                        {
+                            ScopedIndent addr(this);
+                            if (!EmitExpression(var->declared_address_space)) {
+                                return false;
+                            }
+                        }
+                        line() << "]";
+                        if (var->declared_access) {
+                            line() << "access: [";
+                            {
+                                ScopedIndent acs(this);
+                                if (!EmitExpression(var->declared_access)) {
+                                    return false;
+                                }
+                            }
+                            line() << "]";
+                        }
+                    }
+                    line() << "]";
+                } else {
+                    line() << "Var []";
+                }
+                return true;
+            },
+            [&](const ast::Let*) {
+                line() << "Let []";
+                return true;
+            },
+            [&](const ast::Override*) {
+                line() << "Override []";
+                return true;
+            },
+            [&](const ast::Const*) {
+                line() << "Const []";
+                return true;
+            },
+            [&](Default) {
+                TINT_ICE(Writer, diagnostics_) << "unhandled variable type " << v->TypeInfo().name;
+                return false;
+            });
+        if (!ok) {
+            return false;
+        }
+
+        line() << "name: " << program_->Symbols().NameFor(v->name->symbol);
+
+        if (auto ty = v->type) {
+            line() << "type: [";
+            {
+                ScopedIndent vty(this);
+                if (!EmitExpression(ty)) {
+                    return false;
+                }
+            }
+            line() << "]";
+        }
+
+        if (v->initializer != nullptr) {
+            line() << "initializer: [";
+            {
+                ScopedIndent init(this);
+                if (!EmitExpression(v->initializer)) {
+                    return false;
+                }
+            }
+            line() << "]";
+        }
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitAttributes(utils::VectorRef<const ast::Attribute*> attrs) {
+    for (auto* attr : attrs) {
+        bool ok = Switch(
+            attr,
+            [&](const ast::WorkgroupAttribute* workgroup) {
+                auto values = workgroup->Values();
+                line() << "WorkgroupAttribute [";
+                {
+                    ScopedIndent wg(this);
+                    for (size_t i = 0; i < 3; i++) {
+                        if (values[i]) {
+                            if (!EmitExpression(values[i])) {
+                                return false;
+                            }
+                        }
+                    }
+                }
+                line() << "]";
+                return true;
+            },
+            [&](const ast::StageAttribute* stage) {
+                line() << "StageAttribute [" << stage->stage << "]";
+                return true;
+            },
+            [&](const ast::BindingAttribute* binding) {
+                line() << "BindingAttribute [";
+                {
+                    ScopedIndent ba(this);
+                    if (!EmitExpression(binding->expr)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+                return true;
+            },
+            [&](const ast::GroupAttribute* group) {
+                line() << "GroupAttribute [";
+                {
+                    ScopedIndent ga(this);
+                    if (!EmitExpression(group->expr)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+                return true;
+            },
+            [&](const ast::LocationAttribute* location) {
+                line() << "LocationAttribute [";
+                {
+                    ScopedIndent la(this);
+                    if (!EmitExpression(location->expr)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+                return true;
+            },
+            [&](const ast::BuiltinAttribute* builtin) {
+                line() << "BuiltinAttribute [";
+                {
+                    ScopedIndent ba(this);
+                    if (!EmitExpression(builtin->builtin)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+                return true;
+            },
+            [&](const ast::DiagnosticAttribute* diagnostic) {
+                return EmitDiagnosticControl(diagnostic->control);
+            },
+            [&](const ast::InterpolateAttribute* interpolate) {
+                line() << "InterpolateAttribute [";
+                {
+                    ScopedIndent ia(this);
+                    line() << "type: [";
+                    {
+                        ScopedIndent ty(this);
+                        if (!EmitExpression(interpolate->type)) {
+                            return false;
+                        }
+                    }
+                    line() << "]";
+                    if (interpolate->sampling) {
+                        line() << "sampling: [";
+                        {
+                            ScopedIndent sa(this);
+                            if (!EmitExpression(interpolate->sampling)) {
+                                return false;
+                            }
+                        }
+                        line() << "]";
+                    }
+                }
+                line() << "]";
+                return true;
+            },
+            [&](const ast::InvariantAttribute*) {
+                line() << "InvariantAttribute []";
+                return true;
+            },
+            [&](const ast::IdAttribute* override_deco) {
+                line() << "IdAttribute [";
+                {
+                    ScopedIndent id(this);
+                    if (!EmitExpression(override_deco->expr)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+                return true;
+            },
+            [&](const ast::MustUseAttribute*) {
+                line() << "MustUseAttribute []";
+                return true;
+            },
+            [&](const ast::StructMemberOffsetAttribute* offset) {
+                line() << "StructMemberOffsetAttribute [";
+                {
+                    ScopedIndent smoa(this);
+                    if (!EmitExpression(offset->expr)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+                return true;
+            },
+            [&](const ast::StructMemberSizeAttribute* size) {
+                line() << "StructMemberSizeAttribute [";
+                {
+                    ScopedIndent smsa(this);
+                    if (!EmitExpression(size->expr)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+                return true;
+            },
+            [&](const ast::StructMemberAlignAttribute* align) {
+                line() << "StructMemberAlignAttribute [";
+                {
+                    ScopedIndent smaa(this);
+                    if (!EmitExpression(align->expr)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+                return true;
+            },
+            [&](const ast::StrideAttribute* stride) {
+                line() << "StrideAttribute [" << stride->stride << "]";
+                return true;
+            },
+            [&](const ast::InternalAttribute* internal) {
+                line() << "InternalAttribute [" << internal->InternalName() << "]";
+                return true;
+            },
+            [&](Default) {
+                TINT_ICE(Writer, diagnostics_)
+                    << "Unsupported attribute '" << attr->TypeInfo().name << "'";
+                return false;
+            });
+
+        if (!ok) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool GeneratorImpl::EmitBinary(const ast::BinaryExpression* expr) {
+    line() << "BinaryExpression [";
+    {
+        ScopedIndent be(this);
+        line() << "lhs: [";
+        {
+            ScopedIndent lhs(this);
+
+            if (!EmitExpression(expr->lhs)) {
+                return false;
+            }
+        }
+        line() << "]";
+        line() << "op: [";
+        {
+            ScopedIndent op(this);
+            if (!EmitBinaryOp(expr->op)) {
+                return false;
+            }
+        }
+        line() << "]";
+        line() << "rhs: [";
+        {
+            ScopedIndent rhs(this);
+            if (!EmitExpression(expr->rhs)) {
+                return false;
+            }
+        }
+        line() << "]";
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitBinaryOp(const ast::BinaryOp op) {
+    switch (op) {
+        case ast::BinaryOp::kAnd:
+            line() << "&";
+            break;
+        case ast::BinaryOp::kOr:
+            line() << "|";
+            break;
+        case ast::BinaryOp::kXor:
+            line() << "^";
+            break;
+        case ast::BinaryOp::kLogicalAnd:
+            line() << "&&";
+            break;
+        case ast::BinaryOp::kLogicalOr:
+            line() << "||";
+            break;
+        case ast::BinaryOp::kEqual:
+            line() << "==";
+            break;
+        case ast::BinaryOp::kNotEqual:
+            line() << "!=";
+            break;
+        case ast::BinaryOp::kLessThan:
+            line() << "<";
+            break;
+        case ast::BinaryOp::kGreaterThan:
+            line() << ">";
+            break;
+        case ast::BinaryOp::kLessThanEqual:
+            line() << "<=";
+            break;
+        case ast::BinaryOp::kGreaterThanEqual:
+            line() << ">=";
+            break;
+        case ast::BinaryOp::kShiftLeft:
+            line() << "<<";
+            break;
+        case ast::BinaryOp::kShiftRight:
+            line() << ">>";
+            break;
+        case ast::BinaryOp::kAdd:
+            line() << "+";
+            break;
+        case ast::BinaryOp::kSubtract:
+            line() << "-";
+            break;
+        case ast::BinaryOp::kMultiply:
+            line() << "*";
+            break;
+        case ast::BinaryOp::kDivide:
+            line() << "/";
+            break;
+        case ast::BinaryOp::kModulo:
+            line() << "%";
+            break;
+        case ast::BinaryOp::kNone:
+            diagnostics_.add_error(diag::System::Writer, "missing binary operation type");
+            return false;
+    }
+    return true;
+}
+
+bool GeneratorImpl::EmitUnaryOp(const ast::UnaryOpExpression* expr) {
+    line() << "UnaryOpExpression [";
+    {
+        ScopedIndent uoe(this);
+        line() << "op: [";
+        {
+            ScopedIndent op(this);
+            switch (expr->op) {
+                case ast::UnaryOp::kAddressOf:
+                    line() << "&";
+                    break;
+                case ast::UnaryOp::kComplement:
+                    line() << "~";
+                    break;
+                case ast::UnaryOp::kIndirection:
+                    line() << "*";
+                    break;
+                case ast::UnaryOp::kNot:
+                    line() << "!";
+                    break;
+                case ast::UnaryOp::kNegation:
+                    line() << "-";
+                    break;
+            }
+        }
+        line() << "]";
+        line() << "expr: [";
+        {
+            ScopedIndent ex(this);
+            if (!EmitExpression(expr->expr)) {
+                return false;
+            }
+        }
+        line() << "]";
+    }
+    line() << "]";
+
+    return true;
+}
+
+bool GeneratorImpl::EmitBlock(const ast::BlockStatement* stmt) {
+    {
+        if (!EmitBlockHeader(stmt)) {
+            return false;
+        }
+    }
+    if (!EmitStatementsWithIndent(stmt->statements)) {
+        return false;
+    }
+
+    return true;
+}
+
+bool GeneratorImpl::EmitBlockHeader(const ast::BlockStatement* stmt) {
+    if (!stmt->attributes.IsEmpty()) {
+        line() << "attrs: [";
+        {
+            ScopedIndent attrs(this);
+            if (!EmitAttributes(stmt->attributes)) {
+                return false;
+            }
+        }
+        line() << "]";
+    }
+    return true;
+}
+
+bool GeneratorImpl::EmitStatement(const ast::Statement* stmt) {
+    return Switch(
+        stmt,  //
+        [&](const ast::AssignmentStatement* a) { return EmitAssign(a); },
+        [&](const ast::BlockStatement* b) { return EmitBlock(b); },
+        [&](const ast::BreakStatement* b) { return EmitBreak(b); },
+        [&](const ast::BreakIfStatement* b) { return EmitBreakIf(b); },
+        [&](const ast::CallStatement* c) { return EmitCall(c->expr); },
+        [&](const ast::CompoundAssignmentStatement* c) { return EmitCompoundAssign(c); },
+        [&](const ast::ContinueStatement* c) { return EmitContinue(c); },
+        [&](const ast::DiscardStatement* d) { return EmitDiscard(d); },
+        [&](const ast::IfStatement* i) { return EmitIf(i); },
+        [&](const ast::IncrementDecrementStatement* l) { return EmitIncrementDecrement(l); },
+        [&](const ast::LoopStatement* l) { return EmitLoop(l); },
+        [&](const ast::ForLoopStatement* l) { return EmitForLoop(l); },
+        [&](const ast::WhileStatement* l) { return EmitWhile(l); },
+        [&](const ast::ReturnStatement* r) { return EmitReturn(r); },
+        [&](const ast::ConstAssert* c) { return EmitConstAssert(c); },
+        [&](const ast::SwitchStatement* s) { return EmitSwitch(s); },
+        [&](const ast::VariableDeclStatement* v) { return EmitVariable(v->variable); },
+        [&](Default) {
+            diagnostics_.add_error(diag::System::Writer,
+                                   "unknown statement type: " + std::string(stmt->TypeInfo().name));
+            return false;
+        });
+}
+
+bool GeneratorImpl::EmitStatements(utils::VectorRef<const ast::Statement*> stmts) {
+    for (auto* s : stmts) {
+        if (!EmitStatement(s)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool GeneratorImpl::EmitStatementsWithIndent(utils::VectorRef<const ast::Statement*> stmts) {
+    ScopedIndent si(this);
+    return EmitStatements(stmts);
+}
+
+bool GeneratorImpl::EmitAssign(const ast::AssignmentStatement* stmt) {
+    line() << "AssignmentStatement [";
+    {
+        ScopedIndent as(this);
+        line() << "lhs: [";
+        {
+            ScopedIndent lhs(this);
+            if (!EmitExpression(stmt->lhs)) {
+                return false;
+            }
+        }
+        line() << "]";
+        line() << "rhs: [";
+        {
+            ScopedIndent rhs(this);
+            if (!EmitExpression(stmt->rhs)) {
+                return false;
+            }
+            line() << "]";
+        }
+    }
+    line() << "]";
+
+    return true;
+}
+
+bool GeneratorImpl::EmitBreak(const ast::BreakStatement*) {
+    line() << "BreakStatement []";
+    return true;
+}
+
+bool GeneratorImpl::EmitBreakIf(const ast::BreakIfStatement* b) {
+    line() << "BreakIfStatement [";
+    {
+        ScopedIndent bis(this);
+        if (!EmitExpression(b->condition)) {
+            return false;
+        }
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitCase(const ast::CaseStatement* stmt) {
+    line() << "CaseStatement [";
+    {
+        ScopedIndent cs(this);
+        if (stmt->selectors.Length() == 1 && stmt->ContainsDefault()) {
+            line() << "selector: default";
+            if (!EmitBlockHeader(stmt->body)) {
+                return false;
+            }
+        } else {
+            line() << "selectors: [";
+            {
+                ScopedIndent sels(this);
+                for (auto* sel : stmt->selectors) {
+                    if (sel->IsDefault()) {
+                        line() << "default []";
+                    } else if (!EmitExpression(sel->expr)) {
+                        return false;
+                    }
+                }
+            }
+            line() << "]";
+            if (!EmitBlockHeader(stmt->body)) {
+                return false;
+            }
+        }
+        if (!EmitStatementsWithIndent(stmt->body->statements)) {
+            return false;
+        }
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitCompoundAssign(const ast::CompoundAssignmentStatement* stmt) {
+    line() << "CompoundAssignmentStatement [";
+    {
+        ScopedIndent cas(this);
+        line() << "lhs: [";
+        {
+            ScopedIndent lhs(this);
+            if (!EmitExpression(stmt->lhs)) {
+                return false;
+            }
+        }
+        line() << "]";
+
+        line() << "op: [";
+        {
+            ScopedIndent op(this);
+            if (!EmitBinaryOp(stmt->op)) {
+                return false;
+            }
+        }
+        line() << "]";
+        line() << "rhs: [";
+        {
+            ScopedIndent rhs(this);
+
+            if (!EmitExpression(stmt->rhs)) {
+                return false;
+            }
+        }
+        line() << "]";
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitContinue(const ast::ContinueStatement*) {
+    line() << "ContinueStatement []";
+    return true;
+}
+
+bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
+    {
+        line() << "IfStatement [";
+        {
+            ScopedIndent ifs(this);
+            line() << "condition: [";
+            {
+                ScopedIndent cond(this);
+                if (!EmitExpression(stmt->condition)) {
+                    return false;
+                }
+            }
+            line() << "]";
+            if (!EmitBlockHeader(stmt->body)) {
+                return false;
+            }
+        }
+        line() << "] ";
+    }
+    if (!EmitStatementsWithIndent(stmt->body->statements)) {
+        return false;
+    }
+
+    const ast::Statement* e = stmt->else_statement;
+    while (e) {
+        if (auto* elseif = e->As<ast::IfStatement>()) {
+            {
+                line() << "Else IfStatement [";
+                {
+                    ScopedIndent ifs(this);
+                    line() << "condition: [";
+                    if (!EmitExpression(elseif->condition)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+                if (!EmitBlockHeader(elseif->body)) {
+                    return false;
+                }
+            }
+            line() << "]";
+            if (!EmitStatementsWithIndent(elseif->body->statements)) {
+                return false;
+            }
+            e = elseif->else_statement;
+        } else {
+            auto* body = e->As<ast::BlockStatement>();
+            {
+                line() << "Else [";
+                {
+                    ScopedIndent els(this);
+                    if (!EmitBlockHeader(body)) {
+                        return false;
+                    }
+                }
+                line() << "]";
+            }
+            if (!EmitStatementsWithIndent(body->statements)) {
+                return false;
+            }
+            break;
+        }
+    }
+    return true;
+}
+
+bool GeneratorImpl::EmitIncrementDecrement(const ast::IncrementDecrementStatement* stmt) {
+    line() << "IncrementDecrementStatement [";
+    {
+        ScopedIndent ids(this);
+        line() << "expr: [";
+        if (!EmitExpression(stmt->lhs)) {
+            return false;
+        }
+        line() << "]";
+        line() << "dir: " << (stmt->increment ? "++" : "--");
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitDiscard(const ast::DiscardStatement*) {
+    line() << "DiscardStatement []";
+    return true;
+}
+
+bool GeneratorImpl::EmitLoop(const ast::LoopStatement* stmt) {
+    line() << "LoopStatement [";
+    {
+        ScopedIndent ls(this);
+        if (!EmitStatements(stmt->body->statements)) {
+            return false;
+        }
+
+        if (stmt->continuing && !stmt->continuing->Empty()) {
+            line() << "Continuing [";
+            {
+                ScopedIndent cont(this);
+                if (!EmitStatementsWithIndent(stmt->continuing->statements)) {
+                    return false;
+                }
+            }
+            line() << "]";
+        }
+    }
+    line() << "]";
+
+    return true;
+}
+
+bool GeneratorImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
+    TextBuffer init_buf;
+    if (auto* init = stmt->initializer) {
+        TINT_SCOPED_ASSIGNMENT(current_buffer_, &init_buf);
+        if (!EmitStatement(init)) {
+            return false;
+        }
+    }
+
+    TextBuffer cont_buf;
+    if (auto* cont = stmt->continuing) {
+        TINT_SCOPED_ASSIGNMENT(current_buffer_, &cont_buf);
+        if (!EmitStatement(cont)) {
+            return false;
+        }
+    }
+
+    line() << "ForLoopStatement [";
+    {
+        ScopedIndent fs(this);
+
+        line() << "initializer: [";
+        {
+            ScopedIndent init(this);
+            switch (init_buf.lines.size()) {
+                case 0:  // No initializer
+                    break;
+                case 1:  // Single line initializer statement
+                    line() << TrimSuffix(init_buf.lines[0].content, ";");
+                    break;
+                default:  // Block initializer statement
+                    for (size_t i = 1; i < init_buf.lines.size(); i++) {
+                        // Indent all by the first line
+                        init_buf.lines[i].indent += current_buffer_->current_indent;
+                    }
+                    line() << TrimSuffix(init_buf.String(), "\n");
+                    break;
+            }
+        }
+        line() << "]";
+        line() << "condition: [";
+        {
+            ScopedIndent con(this);
+            if (auto* cond = stmt->condition) {
+                if (!EmitExpression(cond)) {
+                    return false;
+                }
+            }
+        }
+
+        line() << "]";
+        line() << "continuing: [";
+        {
+            ScopedIndent cont(this);
+            switch (cont_buf.lines.size()) {
+                case 0:  // No continuing
+                    break;
+                case 1:  // Single line continuing statement
+                    line() << TrimSuffix(cont_buf.lines[0].content, ";");
+                    break;
+                default:  // Block continuing statement
+                    for (size_t i = 1; i < cont_buf.lines.size(); i++) {
+                        // Indent all by the first line
+                        cont_buf.lines[i].indent += current_buffer_->current_indent;
+                    }
+                    line() << TrimSuffix(cont_buf.String(), "\n");
+                    break;
+            }
+        }
+        if (!EmitBlockHeader(stmt->body)) {
+            return false;
+        }
+
+        if (!EmitStatementsWithIndent(stmt->body->statements)) {
+            return false;
+        }
+    }
+    line() << "]";
+
+    return true;
+}
+
+bool GeneratorImpl::EmitWhile(const ast::WhileStatement* stmt) {
+    line() << "WhileStatement [";
+    {
+        ScopedIndent ws(this);
+        {
+            auto* cond = stmt->condition;
+            if (!EmitExpression(cond)) {
+                return false;
+            }
+        }
+        if (!EmitBlockHeader(stmt->body)) {
+            return false;
+        }
+        if (!EmitStatementsWithIndent(stmt->body->statements)) {
+            return false;
+        }
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitReturn(const ast::ReturnStatement* stmt) {
+    line() << "ReturnStatement [";
+    {
+        ScopedIndent ret(this);
+        if (stmt->value) {
+            if (!EmitExpression(stmt->value)) {
+                return false;
+            }
+        }
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitConstAssert(const ast::ConstAssert* stmt) {
+    line() << "ConstAssert [";
+    {
+        ScopedIndent ca(this);
+        if (!EmitExpression(stmt->condition)) {
+            return false;
+        }
+    }
+    line() << "]";
+    return true;
+}
+
+bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
+    line() << "SwitchStatement [";
+    {
+        ScopedIndent ss(this);
+        line() << "condition: [";
+        {
+            ScopedIndent cond(this);
+            if (!EmitExpression(stmt->condition)) {
+                return false;
+            }
+        }
+        line() << "]";
+
+        {
+            ScopedIndent si(this);
+            for (auto* s : stmt->body) {
+                if (!EmitCase(s)) {
+                    return false;
+                }
+            }
+        }
+    }
+    line() << "]";
+    return true;
+}
+
+}  // namespace tint::writer::syntax_tree
diff --git a/src/tint/writer/syntax_tree/generator_impl.h b/src/tint/writer/syntax_tree/generator_impl.h
new file mode 100644
index 0000000..8b9a187
--- /dev/null
+++ b/src/tint/writer/syntax_tree/generator_impl.h
@@ -0,0 +1,215 @@
+// Copyright 2023 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_TINT_WRITER_SYNTAX_TREE_GENERATOR_IMPL_H_
+#define SRC_TINT_WRITER_SYNTAX_TREE_GENERATOR_IMPL_H_
+
+#include <string>
+
+#include "src/tint/ast/assignment_statement.h"
+#include "src/tint/ast/binary_expression.h"
+#include "src/tint/ast/bitcast_expression.h"
+#include "src/tint/ast/break_if_statement.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/compound_assignment_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/for_loop_statement.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/index_accessor_expression.h"
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ast/member_accessor_expression.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/switch_statement.h"
+#include "src/tint/ast/unary_op_expression.h"
+#include "src/tint/program.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/utils/string_stream.h"
+#include "src/tint/writer/text_generator.h"
+
+namespace tint::writer::syntax_tree {
+
+/// Implementation class for AST generator
+class GeneratorImpl : public TextGenerator {
+  public:
+    /// Constructor
+    /// @param program the program
+    explicit GeneratorImpl(const Program* program);
+    ~GeneratorImpl();
+
+    /// Generates the result data
+    /// @returns true on successful generation; false otherwise
+    bool Generate();
+
+    /// Handles generating a diagnostic control
+    /// @param diagnostic the diagnostic control node
+    /// @returns true if the diagnostic control was emitted
+    bool EmitDiagnosticControl(const ast::DiagnosticControl& diagnostic);
+    /// Handles generating an enable directive
+    /// @param enable the enable node
+    /// @returns true if the enable directive was emitted
+    bool EmitEnable(const ast::Enable* enable);
+    /// Handles generating a declared type
+    /// @param ty the declared type to generate
+    /// @returns true if the declared type was emitted
+    bool EmitTypeDecl(const ast::TypeDecl* ty);
+    /// Handles an index accessor expression
+    /// @param expr the expression to emit
+    /// @returns true if the index accessor was emitted
+    bool EmitIndexAccessor(const ast::IndexAccessorExpression* expr);
+    /// Handles an assignment statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted successfully
+    bool EmitAssign(const ast::AssignmentStatement* stmt);
+    /// Handles generating a binary expression
+    /// @param expr the binary expression
+    /// @returns true if the expression was emitted, false otherwise
+    bool EmitBinary(const ast::BinaryExpression* expr);
+    /// Handles generating a binary operator
+    /// @param op the binary operator
+    /// @returns true if the operator was emitted, false otherwise
+    bool EmitBinaryOp(const ast::BinaryOp op);
+    /// Handles generating a bitcast expression
+    /// @param expr the bitcast expression
+    /// @returns true if the bitcast was emitted
+    bool EmitBitcast(const ast::BitcastExpression* expr);
+    /// Handles a block statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted successfully
+    bool EmitBlock(const ast::BlockStatement* stmt);
+    /// Handles emitting the start of a block statement (including attributes)
+    /// @param stmt the block statement to emit the header for
+    /// @returns true if the statement was emitted successfully
+    bool EmitBlockHeader(const ast::BlockStatement* stmt);
+    /// Handles a break statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted successfully
+    bool EmitBreak(const ast::BreakStatement* stmt);
+    /// Handles a break-if statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted successfully
+    bool EmitBreakIf(const ast::BreakIfStatement* stmt);
+    /// Handles generating a call expression
+    /// @param expr the call expression
+    /// @returns true if the call expression is emitted
+    bool EmitCall(const ast::CallExpression* expr);
+    /// Handles a case statement
+    /// @param stmt the statement
+    /// @returns true if the statment was emitted successfully
+    bool EmitCase(const ast::CaseStatement* stmt);
+    /// Handles a compound assignment statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted successfully
+    bool EmitCompoundAssign(const ast::CompoundAssignmentStatement* stmt);
+    /// Handles generating a literal expression
+    /// @param expr the literal expression expression
+    /// @returns true if the literal expression is emitted
+    bool EmitLiteral(const ast::LiteralExpression* expr);
+    /// Handles a continue statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted successfully
+    bool EmitContinue(const ast::ContinueStatement* stmt);
+    /// Handles generate an Expression
+    /// @param expr the expression
+    /// @returns true if the expression was emitted
+    bool EmitExpression(const ast::Expression* expr);
+    /// Handles generating a function
+    /// @param func the function to generate
+    /// @returns true if the function was emitted
+    bool EmitFunction(const ast::Function* func);
+    /// Handles generating an identifier expression
+    /// @param expr the identifier expression
+    /// @returns true if the identifier was emitted
+    bool EmitIdentifier(const ast::IdentifierExpression* expr);
+    /// Handles generating an identifier
+    /// @param ident the identifier
+    /// @returns true if the identifier was emitted
+    bool EmitIdentifier(const ast::Identifier* ident);
+    /// Handles an if statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was successfully emitted
+    bool EmitIf(const ast::IfStatement* stmt);
+    /// Handles an increment/decrement statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was successfully emitted
+    bool EmitIncrementDecrement(const ast::IncrementDecrementStatement* stmt);
+    /// Handles generating a discard statement
+    /// @param stmt the discard statement
+    /// @returns true if the statement was successfully emitted
+    bool EmitDiscard(const ast::DiscardStatement* stmt);
+    /// Handles a loop statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emtited
+    bool EmitLoop(const ast::LoopStatement* stmt);
+    /// Handles a for-loop statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emtited
+    bool EmitForLoop(const ast::ForLoopStatement* stmt);
+    /// Handles a while statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emtited
+    bool EmitWhile(const ast::WhileStatement* stmt);
+    /// Handles a member accessor expression
+    /// @param expr the member accessor expression
+    /// @returns true if the member accessor was emitted
+    bool EmitMemberAccessor(const ast::MemberAccessorExpression* expr);
+    /// Handles return statements
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was successfully emitted
+    bool EmitReturn(const ast::ReturnStatement* stmt);
+    /// Handles const assertion statements
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was successfully emitted
+    bool EmitConstAssert(const ast::ConstAssert* stmt);
+    /// Handles statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted
+    bool EmitStatement(const ast::Statement* stmt);
+    /// Handles a statement list
+    /// @param stmts the statements to emit
+    /// @returns true if the statements were emitted
+    bool EmitStatements(utils::VectorRef<const ast::Statement*> stmts);
+    /// Handles a statement list with an increased indentation
+    /// @param stmts the statements to emit
+    /// @returns true if the statements were emitted
+    bool EmitStatementsWithIndent(utils::VectorRef<const ast::Statement*> stmts);
+    /// Handles generating a switch statement
+    /// @param stmt the statement to emit
+    /// @returns true if the statement was emitted
+    bool EmitSwitch(const ast::SwitchStatement* stmt);
+    /// Handles generating a struct declaration
+    /// @param str the struct
+    /// @returns true if the struct is emitted
+    bool EmitStructType(const ast::Struct* str);
+    /// Handles emitting an image format
+    /// @param fmt the format to generate
+    /// @returns true if the format is emitted
+    bool EmitImageFormat(const builtin::TexelFormat fmt);
+    /// Handles a unary op expression
+    /// @param expr the expression to emit
+    /// @returns true if the expression was emitted
+    bool EmitUnaryOp(const ast::UnaryOpExpression* expr);
+    /// Handles generating a variable
+    /// @param var the variable to generate
+    /// @returns true if the variable was emitted
+    bool EmitVariable(const ast::Variable* var);
+    /// Handles generating a attribute list
+    /// @param attrs the attribute list
+    /// @returns true if the attributes were emitted
+    bool EmitAttributes(utils::VectorRef<const ast::Attribute*> attrs);
+};
+
+}  // namespace tint::writer::syntax_tree
+
+#endif  // SRC_TINT_WRITER_SYNTAX_TREE_GENERATOR_IMPL_H_
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index 69aaabd..2b5f474 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -34,6 +34,8 @@
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/sem/struct.h"
 #include "src/tint/sem/switch_statement.h"
+#include "src/tint/switch.h"
+#include "src/tint/utils/defer.h"
 #include "src/tint/utils/math.h"
 #include "src/tint/utils/scoped_assignment.h"
 #include "src/tint/writer/float_to_string.h"
@@ -98,7 +100,14 @@
 
 bool GeneratorImpl::EmitEnable(const ast::Enable* enable) {
     auto out = line();
-    out << "enable " << enable->extension << ";";
+    out << "enable ";
+    for (auto* ext : enable->extensions) {
+        if (ext != enable->extensions.Front()) {
+            out << ", ";
+        }
+        out << ext->name;
+    }
+    out << ";";
     return true;
 }
 
@@ -940,6 +949,14 @@
 bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
     {
         auto out = line();
+
+        if (!stmt->attributes.IsEmpty()) {
+            if (!EmitAttributes(out, stmt->attributes)) {
+                return false;
+            }
+            out << " ";
+        }
+
         out << "if (";
         if (!EmitExpression(out, stmt->condition)) {
             return false;
@@ -1049,6 +1066,14 @@
 
     {
         auto out = line();
+
+        if (!stmt->attributes.IsEmpty()) {
+            if (!EmitAttributes(out, stmt->attributes)) {
+                return false;
+            }
+            out << " ";
+        }
+
         out << "for";
         {
             ScopedParen sp(out);
@@ -1110,6 +1135,14 @@
 bool GeneratorImpl::EmitWhile(const ast::WhileStatement* stmt) {
     {
         auto out = line();
+
+        if (!stmt->attributes.IsEmpty()) {
+            if (!EmitAttributes(out, stmt->attributes)) {
+                return false;
+            }
+            out << " ";
+        }
+
         out << "while";
         {
             ScopedParen sp(out);
@@ -1160,6 +1193,14 @@
 bool GeneratorImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
     {
         auto out = line();
+
+        if (!stmt->attributes.IsEmpty()) {
+            if (!EmitAttributes(out, stmt->attributes)) {
+                return false;
+            }
+            out << " ";
+        }
+
         out << "switch(";
         if (!EmitExpression(out, stmt->condition)) {
             return false;
diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt
index bf1542c..3ee0d99 100644
--- a/third_party/CMakeLists.txt
+++ b/third_party/CMakeLists.txt
@@ -51,3 +51,18 @@
     add_subdirectory("${TINT_THIRD_PARTY_DIR}/vulkan-deps/glslang/src" "${CMAKE_BINARY_DIR}/third_party/glslang" EXCLUDE_FROM_ALL)
   endif()
 endif()
+
+if (NOT TARGET libabsl)
+    if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") OR
+        ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang"))
+        add_compile_options(
+            -Wno-array-parameter
+            -Wno-deprecated-builtins
+            -Wno-unknown-warning-option
+        )
+    endif()
+
+    # Recommended setting for compability with future abseil releases.
+    set(ABSL_PROPAGATE_CXX_STD ON)
+    add_subdirectory("${TINT_THIRD_PARTY_DIR}/abseil-cpp" "${CMAKE_CURRENT_BINARY_DIR}/abseil")
+endif()
diff --git a/third_party/gn/abseil-cpp/BUILD.gn b/third_party/gn/abseil-cpp/BUILD.gn
new file mode 100644
index 0000000..60c9406
--- /dev/null
+++ b/third_party/gn/abseil-cpp/BUILD.gn
@@ -0,0 +1,168 @@
+# Copyright 2021 The Dawn 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.
+
+import("../../../scripts/dawn_overrides_with_defaults.gni")
+
+config("absl_config") {
+  if (is_clang) {
+    cflags = [
+      # Allow the use of enable_if()
+      "-Wno-gcc-compat",
+    ]
+  }
+
+  include_dirs = [ "${dawn_abseil_dir}" ]
+}
+
+template("absl_source_set") {
+  source_set(target_name) {
+    forward_variables_from(invoker, "*")
+
+    if (!defined(public_configs)) {
+      public_configs = []
+    }
+    public_configs += [ ":absl_config" ]
+  }
+}
+
+#
+# absl/base
+#
+
+absl_source_set("log_severity") {
+  sources = [ "${dawn_abseil_dir}/absl/base/log_severity.cc" ]
+  public = [ "${dawn_abseil_dir}/absl/base/log_severity.h" ]
+}
+
+absl_source_set("raw_logging_internal") {
+  sources = [ "${dawn_abseil_dir}/absl/base/internal/raw_logging.cc" ]
+  public = [ "${dawn_abseil_dir}/absl/base/internal/raw_logging.h" ]
+  public_deps = [ ":log_severity" ]
+  visibility = [ ":*" ]
+}
+
+absl_source_set("throw_delegate") {
+  sources = [ "${dawn_abseil_dir}/absl/base/internal/throw_delegate.cc" ]
+  public = [ "${dawn_abseil_dir}/absl/base/internal/throw_delegate.h" ]
+  public_deps = [ ":raw_logging_internal" ]
+  visibility = [ ":*" ]
+}
+
+#
+# absl/numeric
+#
+
+absl_source_set("int128") {
+  sources = [
+    "${dawn_abseil_dir}/absl/numeric/int128.cc",
+    "${dawn_abseil_dir}/absl/numeric/int128_have_intrinsic.inc",
+    "${dawn_abseil_dir}/absl/numeric/int128_no_intrinsic.inc",
+  ]
+  public = [ "${dawn_abseil_dir}/absl/numeric/int128.h" ]
+}
+
+#
+# absl/strings
+#
+
+absl_source_set("strings") {
+  sources = [
+    "${dawn_abseil_dir}/absl/strings/ascii.cc",
+    "${dawn_abseil_dir}/absl/strings/charconv.cc",
+    "${dawn_abseil_dir}/absl/strings/escaping.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/charconv_bigint.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/charconv_bigint.h",
+    "${dawn_abseil_dir}/absl/strings/internal/charconv_parse.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/charconv_parse.h",
+    "${dawn_abseil_dir}/absl/strings/internal/memutil.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/memutil.h",
+    "${dawn_abseil_dir}/absl/strings/internal/stl_type_traits.h",
+    "${dawn_abseil_dir}/absl/strings/internal/str_join_internal.h",
+    "${dawn_abseil_dir}/absl/strings/internal/str_split_internal.h",
+    "${dawn_abseil_dir}/absl/strings/match.cc",
+    "${dawn_abseil_dir}/absl/strings/numbers.cc",
+    "${dawn_abseil_dir}/absl/strings/str_cat.cc",
+    "${dawn_abseil_dir}/absl/strings/str_replace.cc",
+    "${dawn_abseil_dir}/absl/strings/str_split.cc",
+    "${dawn_abseil_dir}/absl/strings/string_view.cc",
+    "${dawn_abseil_dir}/absl/strings/substitute.cc",
+  ]
+  public = [
+    "${dawn_abseil_dir}/absl/strings/ascii.h",
+    "${dawn_abseil_dir}/absl/strings/charconv.h",
+    "${dawn_abseil_dir}/absl/strings/escaping.h",
+    "${dawn_abseil_dir}/absl/strings/internal/string_constant.h",
+    "${dawn_abseil_dir}/absl/strings/match.h",
+    "${dawn_abseil_dir}/absl/strings/numbers.h",
+    "${dawn_abseil_dir}/absl/strings/str_cat.h",
+    "${dawn_abseil_dir}/absl/strings/str_join.h",
+    "${dawn_abseil_dir}/absl/strings/str_replace.h",
+    "${dawn_abseil_dir}/absl/strings/str_split.h",
+    "${dawn_abseil_dir}/absl/strings/string_view.h",
+    "${dawn_abseil_dir}/absl/strings/strip.h",
+    "${dawn_abseil_dir}/absl/strings/substitute.h",
+  ]
+  deps = [
+    ":int128",
+    ":raw_logging_internal",
+    ":strings_internal",
+    ":throw_delegate",
+  ]
+}
+
+absl_source_set("strings_internal") {
+  sources = [
+    "${dawn_abseil_dir}/absl/strings/internal/escaping.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/ostringstream.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/utf8.cc",
+  ]
+  public = [
+    "${dawn_abseil_dir}/absl/strings/internal/char_map.h",
+    "${dawn_abseil_dir}/absl/strings/internal/escaping.h",
+    "${dawn_abseil_dir}/absl/strings/internal/ostringstream.h",
+    "${dawn_abseil_dir}/absl/strings/internal/resize_uninitialized.h",
+    "${dawn_abseil_dir}/absl/strings/internal/utf8.h",
+  ]
+  deps = [ ":raw_logging_internal" ]
+}
+
+absl_source_set("str_format") {
+  public = [ "${dawn_abseil_dir}/absl/strings/str_format.h" ]
+  deps = [ ":str_format_internal" ]
+}
+
+absl_source_set("str_format_internal") {
+  sources = [
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/arg.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/bind.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/extension.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/float_conversion.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/output.cc",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/parser.cc",
+  ]
+  public = [
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/arg.h",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/bind.h",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/checker.h",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/extension.h",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/float_conversion.h",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/output.h",
+    "${dawn_abseil_dir}/absl/strings/internal/str_format/parser.h",
+  ]
+  visibility = [ ":*" ]
+  deps = [
+    ":int128",
+    ":strings",
+  ]
+}
diff --git a/tint_overrides_with_defaults.gni b/tint_overrides_with_defaults.gni
index 9e39248..c54915d 100644
--- a/tint_overrides_with_defaults.gni
+++ b/tint_overrides_with_defaults.gni
@@ -72,6 +72,11 @@
     tint_build_glsl_writer = true
   }
 
+  # Build the Syntax Tree writer
+  if (!defined(tint_build_syntax_tree_writer)) {
+    tint_build_syntax_tree_writer = false
+  }
+
   # Build unittests
   if (!defined(tint_build_unittests)) {
     tint_build_unittests = true