Import Tint changes from Dawn

Changes:
  - 15e7f94b762e86dec964bd040230271c39134df3 Remove ArrayCount helpers. by dan sinclair <dsinclair@chromium.org>
  - 4b1d79e29281c403e9a24c99dfde0b8e60b38487 Moved sem::ArrayCount to an inherited structure by dan sinclair <dsinclair@chromium.org>
  - 847cfa07c57e10f200329ac2c6eca16a8c2d93bb Move allocator into TypeManager by dan sinclair <dsinclair@chromium.org>
  - 2939c4531e84b22f49900b606cf56f0af4351d10 Fix stack-overflow in `lhs_expression`. by dan sinclair <dsinclair@chromium.org>
  - 6992f51ebd3feefb50b03e72cfc9170e5dd46793 tint: Add DirectVariableAccess transform by Ben Clayton <bclayton@google.com>
  - d257e287929813fae395a2d6766fe305d883ce1e tint: Don't override alignment with @offset by Ben Clayton <bclayton@google.com>
  - efb17b02543fb52c0b2e21d6082c0c9fbc2168a9 tint/writer/wgsl: Print @offset attributes as comment by Ben Clayton <bclayton@google.com>
  - f745e4e2bfacedfd6e127888c0ea55177a078ecf Add Source to sem::Struct by dan sinclair <dsinclair@chromium.org>
  - 8954189545af1423f7885afc7b9bee5778c49253 tint_common_fuzzer: Don't attempt to use an invalid program by Ben Clayton <bclayton@google.com>
  - 527e38b68bb2615a4277a6efae9271ba7291d8ea [sem] Move TransitivelyReferencedOverrides to sem::Info. by dan sinclair <dsinclair@chromium.org>
  - 331a3b7980ccd21ec06888acf6acbba934cddde0 Add Source to sem::StructMember by dan sinclair <dsinclair@chromium.org>
  - c5b1b5c77ad375f0706f9db305618f69274e6cb6 Access struct member name through sem. by dan sinclair <dsinclair@chromium.org>
  - d5d207ba9f5c44a578b6d790f28eada38687fab6 tint: Add builtin type aliases (vec3f, etc) by Ben Clayton <bclayton@google.com>
  - f62aec2dc86e41c6b5b61b891f2cb4909cac1fa4 tint: Add missing tests / benchmarks by Ben Clayton <bclayton@google.com>
  - f1f6e6fbea4e0fb0744f9300d6e1fbe25a595e1d tint: merge const eval shift left definitions by Antonio Maiorano <amaiorano@google.com>
  - a54df5eca5dcc90bc36d4fe3ece4bce8a036faa0 Tint: Fix extractBits polyfill by Zhaoming Jiang <zhaoming.jiang@intel.com>
  - 85ceb08d5ca7896605878551ad164d293dcc16d7 Add ExternalTexture Rotate and FlipY Functionality by Brandon Jones <brandon1.jones@intel.com>
  - 94706ecba727e2917e1a3104edaa4a755f39a816 tint: Add 'chromium_experimental_full_ptr_parameters' ext... by Ben Clayton <bclayton@google.com>
  - 53f646c55c973be64daf54b3deced5bf2e5ee29a [ir] Split Binary from Instruction by dan sinclair <dsinclair@chromium.org>
  - babc21f9316878f04a8968bef870453dabd8ee3c tint: const eval of modulo operator by Antonio Maiorano <amaiorano@google.com>
  - 171c542bf791f57400f3211180a94c671c6d58dd tint: improve compile error when calling builder::Val wit... by Antonio Maiorano <amaiorano@google.com>
  - 6e306d34b5067ee01144bb83bf2330c7a32beb40 [ir] Remove operator<< by dan sinclair <dsinclair@chromium.org>
  - a160ccb8c3f309064ba9e14656c4c958454ea2cd tint/ir: Fix build by Ben Clayton <bclayton@google.com>
  - cfea22092700ac50f0d5d314c8aad959dc8728bc tint/number: add CheckedMod functions by Antonio Maiorano <amaiorano@google.com>
  - 4c593c356b165b604396937839f6aba3aa592b0a tint/sem: Minor cleanup by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: 15e7f94b762e86dec964bd040230271c39134df3
Change-Id: I0da9b543283ca4c662dd1f13537bedc5c07ffee5
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/112540
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 3741764..6a0e70d 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -408,6 +408,8 @@
     "resolver/resolver.h",
     "resolver/sem_helper.cc",
     "resolver/sem_helper.h",
+    "resolver/type_alias.cc",
+    "resolver/type_alias.h",
     "resolver/uniformity.cc",
     "resolver/uniformity.h",
     "resolver/validator.cc",
@@ -417,6 +419,7 @@
     "sem/abstract_int.h",
     "sem/abstract_numeric.h",
     "sem/array.h",
+    "sem/array_count.h",
     "sem/atomic.h",
     "sem/behavior.h",
     "sem/binding_point.h",
@@ -500,6 +503,8 @@
     "transform/decompose_strided_matrix.h",
     "transform/demote_to_helper.cc",
     "transform/demote_to_helper.h",
+    "transform/direct_variable_access.cc",
+    "transform/direct_variable_access.h",
     "transform/disable_uniformity_analysis.cc",
     "transform/disable_uniformity_analysis.h",
     "transform/expand_compound_assignment.cc",
@@ -631,6 +636,8 @@
     "sem/abstract_numeric.h",
     "sem/array.cc",
     "sem/array.h",
+    "sem/array_count.cc",
+    "sem/array_count.h",
     "sem/atomic.cc",
     "sem/atomic.h",
     "sem/behavior.cc",
@@ -1016,6 +1023,7 @@
 
   tint_unittests_source_set("tint_unittests_ast_src") {
     sources = [
+      "ast/access_test.cc",
       "ast/address_space_test.cc",
       "ast/alias_test.cc",
       "ast/array_test.cc",
@@ -1224,6 +1232,7 @@
       "transform/decompose_strided_array_test.cc",
       "transform/decompose_strided_matrix_test.cc",
       "transform/demote_to_helper_test.cc",
+      "transform/direct_variable_access_test.cc",
       "transform/disable_uniformity_analysis_test.cc",
       "transform/expand_compound_assignment_test.cc",
       "transform/first_index_offset_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 15532b2..72cec7b 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -299,6 +299,8 @@
   sem/abstract_numeric.h
   sem/array.cc
   sem/array.h
+  sem/array_count.cc
+  sem/array_count.h
   sem/atomic.cc
   sem/atomic.h
   sem/behavior.cc
@@ -425,6 +427,8 @@
   transform/decompose_strided_matrix.h
   transform/demote_to_helper.cc
   transform/demote_to_helper.h
+  transform/direct_variable_access.cc
+  transform/direct_variable_access.h
   transform/disable_uniformity_analysis.cc
   transform/disable_uniformity_analysis.h
   transform/expand_compound_assignment.cc
@@ -535,13 +539,14 @@
   writer/writer.h
 )
 
-tint_generated(ast/access)
+tint_generated(ast/access BENCH TEST)
 tint_generated(ast/address_space BENCH TEST)
 tint_generated(ast/builtin_value BENCH TEST)
 tint_generated(ast/extension BENCH TEST)
-tint_generated(ast/interpolate_attribute)
+tint_generated(ast/interpolate_attribute BENCH TEST)
 tint_generated(ast/texel_format BENCH TEST)
 tint_generated(resolver/init_conv_intrinsic)
+tint_generated(resolver/type_alias BENCH TEST)
 tint_generated(sem/builtin_type)
 tint_generated(sem/parameter_usage)
 
@@ -650,6 +655,8 @@
 
 if(${TINT_BUILD_IR})
   list(APPEND TINT_LIB_SRCS
+    ir/binary.cc
+    ir/binary.h
     ir/block.cc
     ir/block.h
     ir/builder.cc
@@ -807,7 +814,6 @@
     ast/increment_decrement_statement_test.cc
     ast/index_accessor_expression_test.cc
     ast/int_literal_expression_test.cc
-    ast/interpolate_attribute_test.cc
     ast/invariant_attribute_test.cc
     ast/location_attribute_test.cc
     ast/loop_statement_test.cc
@@ -1193,6 +1199,7 @@
       transform/decompose_strided_array_test.cc
       transform/decompose_strided_matrix_test.cc
       transform/demote_to_helper_test.cc
+      transform/direct_variable_access_test.cc
       transform/disable_uniformity_analysis_test.cc
       transform/expand_compound_assignment_test.cc
       transform/first_index_offset_test.cc
@@ -1344,9 +1351,9 @@
 
   if (${TINT_BUILD_IR})
     list(APPEND TINT_TEST_SRCS
+      ir/binary_test.cc
       ir/builder_impl_test.cc
       ir/constant_test.cc
-      ir/instruction_test.cc
       ir/temp_test.cc
       ir/test_helper.h
     )
diff --git a/src/tint/ast/access_bench.cc b/src/tint/ast/access_bench.cc
new file mode 100644
index 0000000..46b4f93
--- /dev/null
+++ b/src/tint/ast/access_bench.cc
@@ -0,0 +1,51 @@
+// 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/access_bench.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/ast/access.h"
+
+#include <array>
+
+#include "benchmark/benchmark.h"
+
+namespace tint::ast {
+namespace {
+
+void AccessParser(::benchmark::State& state) {
+    std::array kStrings{
+        "ccad",       "3",           "rVad",         "read",       "1ead",
+        "rqaJ",       "rlla77",      "reqqdppriHHe", "rv_wcit",    "reabGwrte",
+        "read_write", "read_vriite", "re8d_wriWWe",  "Meadxxrite", "wggte",
+        "VtX",        "writ3",       "write",        "writE",      "TTrPte",
+        "wxxidd",
+    };
+    for (auto _ : state) {
+        for (auto& str : kStrings) {
+            auto result = ParseAccess(str);
+            benchmark::DoNotOptimize(result);
+        }
+    }
+}
+
+BENCHMARK(AccessParser);
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/access_bench.cc.tmpl b/src/tint/ast/access_bench.cc.tmpl
new file mode 100644
index 0000000..e4ef341
--- /dev/null
+++ b/src/tint/ast/access_bench.cc.tmpl
@@ -0,0 +1,29 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate access_bench.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
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "access") -}}
+
+#include "src/tint/ast/access.h"
+
+#include <array>
+
+#include "benchmark/benchmark.h"
+
+namespace tint::ast {
+namespace {
+
+{{ Eval "BenchmarkParseEnum" $enum }}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/access_test.cc b/src/tint/ast/access_test.cc
new file mode 100644
index 0000000..017915d
--- /dev/null
+++ b/src/tint/ast/access_test.cc
@@ -0,0 +1,82 @@
+// 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/access_test.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/ast/access.h"
+
+#include <string>
+
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint::ast {
+namespace {
+
+namespace parse_print_tests {
+
+struct Case {
+    const char* string;
+    Access value;
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+    return out << "'" << std::string(c.string) << "'";
+}
+
+static constexpr Case kValidCases[] = {
+    {"read", Access::kRead},
+    {"read_write", Access::kReadWrite},
+    {"write", Access::kWrite},
+};
+
+static constexpr Case kInvalidCases[] = {
+    {"ccad", Access::kUndefined},       {"3", Access::kUndefined},
+    {"rVad", Access::kUndefined},       {"read1write", Access::kUndefined},
+    {"reaJqqrite", Access::kUndefined}, {"rea7ll_write", Access::kUndefined},
+    {"wrqHtpp", Access::kUndefined},    {"ve", Access::kUndefined},
+    {"Grbe", Access::kUndefined},
+};
+
+using AccessParseTest = testing::TestWithParam<Case>;
+
+TEST_P(AccessParseTest, Parse) {
+    const char* string = GetParam().string;
+    Access expect = GetParam().value;
+    EXPECT_EQ(expect, ParseAccess(string));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, AccessParseTest, testing::ValuesIn(kValidCases));
+INSTANTIATE_TEST_SUITE_P(InvalidCases, AccessParseTest, testing::ValuesIn(kInvalidCases));
+
+using AccessPrintTest = testing::TestWithParam<Case>;
+
+TEST_P(AccessPrintTest, Print) {
+    Access value = GetParam().value;
+    const char* expect = GetParam().string;
+    EXPECT_EQ(expect, utils::ToString(value));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, AccessPrintTest, testing::ValuesIn(kValidCases));
+
+}  // namespace parse_print_tests
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/access_test.cc.tmpl b/src/tint/ast/access_test.cc.tmpl
new file mode 100644
index 0000000..9b4b71a
--- /dev/null
+++ b/src/tint/ast/access_test.cc.tmpl
@@ -0,0 +1,30 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate access_test.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
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "access") -}}
+
+#include "src/tint/ast/access.h"
+
+#include <string>
+
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint::ast {
+namespace {
+
+{{ Eval "TestParsePrintEnum" $enum}}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/extension.cc b/src/tint/ast/extension.cc
index be16806..31a8083 100644
--- a/src/tint/ast/extension.cc
+++ b/src/tint/ast/extension.cc
@@ -34,6 +34,9 @@
     if (str == "chromium_experimental_dp4a") {
         return Extension::kChromiumExperimentalDp4A;
     }
+    if (str == "chromium_experimental_full_ptr_parameters") {
+        return Extension::kChromiumExperimentalFullPtrParameters;
+    }
     if (str == "chromium_experimental_push_constant") {
         return Extension::kChromiumExperimentalPushConstant;
     }
@@ -51,6 +54,8 @@
             return out << "chromium_disable_uniformity_analysis";
         case Extension::kChromiumExperimentalDp4A:
             return out << "chromium_experimental_dp4a";
+        case Extension::kChromiumExperimentalFullPtrParameters:
+            return out << "chromium_experimental_full_ptr_parameters";
         case Extension::kChromiumExperimentalPushConstant:
             return out << "chromium_experimental_push_constant";
         case Extension::kF16:
diff --git a/src/tint/ast/extension.h b/src/tint/ast/extension.h
index f6fa03f..062e3fc 100644
--- a/src/tint/ast/extension.h
+++ b/src/tint/ast/extension.h
@@ -35,6 +35,7 @@
     kUndefined,
     kChromiumDisableUniformityAnalysis,
     kChromiumExperimentalDp4A,
+    kChromiumExperimentalFullPtrParameters,
     kChromiumExperimentalPushConstant,
     kF16,
 };
@@ -52,6 +53,7 @@
 constexpr const char* kExtensionStrings[] = {
     "chromium_disable_uniformity_analysis",
     "chromium_experimental_dp4a",
+    "chromium_experimental_full_ptr_parameters",
     "chromium_experimental_push_constant",
     "f16",
 };
diff --git a/src/tint/ast/extension_bench.cc b/src/tint/ast/extension_bench.cc
index 94b5216..5f175cc 100644
--- a/src/tint/ast/extension_bench.cc
+++ b/src/tint/ast/extension_bench.cc
@@ -45,20 +45,27 @@
         "chromium_exverimentiil_dp4a",
         "chro8ium_experimenWWal_dp4a",
         "chromiMm_eperimxxntal_dp4a",
-        "chrXmium_experimeggtal_ush_constant",
-        "chromiu_experVmentalpusX_constant",
-        "chro3ium_experimental_push_constant",
+        "chromium_expeggimeXtal_full_ptr_paraeters",
+        "chromium_expVrimental_full_ptr_puraXeer",
+        "chromium_experimental_full_ptr3parameters",
+        "chromium_experimental_full_ptr_parameters",
+        "chromium_experimentalEfull_ptr_parameters",
+        "chromium_experimentalfull_ptr_PPaTTameters",
+        "chromium_ddxperimental_fullptrxxparameters",
+        "c44romium_experimental_push_constant",
+        "chromium_experimental_pSSsVV_constant",
+        "chrom22Rm_experimental_pushRonstant",
         "chromium_experimental_push_constant",
-        "chromium_experEmental_push_constant",
-        "chPPomiumexperimental_push_conTTtant",
-        "chromixxm_experimentddl_push_constnt",
-        "4416",
-        "fSVV6",
-        "RR2",
+        "chromium_exp9rimFntal_ush_constant",
+        "chrmium_experimental_push_constant",
+        "cOOromium_experiVeHtal_puh_conRRtant",
+        "y1",
+        "l77rrn6",
+        "4016",
         "f16",
-        "96",
-        "f1",
-        "VOR6",
+        "5",
+        "u16",
+        "f",
     };
     for (auto _ : state) {
         for (auto& str : kStrings) {
diff --git a/src/tint/ast/extension_test.cc b/src/tint/ast/extension_test.cc
index 2879bf8..ea08dcc 100644
--- a/src/tint/ast/extension_test.cc
+++ b/src/tint/ast/extension_test.cc
@@ -44,6 +44,8 @@
 static constexpr Case kValidCases[] = {
     {"chromium_disable_uniformity_analysis", Extension::kChromiumDisableUniformityAnalysis},
     {"chromium_experimental_dp4a", Extension::kChromiumExperimentalDp4A},
+    {"chromium_experimental_full_ptr_parameters",
+     Extension::kChromiumExperimentalFullPtrParameters},
     {"chromium_experimental_push_constant", Extension::kChromiumExperimentalPushConstant},
     {"f16", Extension::kF16},
 };
@@ -55,12 +57,15 @@
     {"chro1ium_experimental_dp4a", Extension::kUndefined},
     {"chrJmium_experiqqetal_dp4a", Extension::kUndefined},
     {"chromium_experimenll77l_dp4a", Extension::kUndefined},
-    {"cppromium_experiHHenal_qqush_constant", Extension::kUndefined},
-    {"chromium_xpericental_sh_vonstant", Extension::kUndefined},
-    {"chromium_experimental_Gsh_cbnstant", Extension::kUndefined},
-    {"f1vi", Extension::kUndefined},
-    {"f8WW", Extension::kUndefined},
-    {"fxx", Extension::kUndefined},
+    {"chroium_experimental_full_ptr_paqqppmetHHrs", Extension::kUndefined},
+    {"chrium_evperiental_full_ptr_paraceters", Extension::kUndefined},
+    {"chromium_expGimental_fullbptr_parameters", Extension::kUndefined},
+    {"chvomium_experimental_push_constiint", Extension::kUndefined},
+    {"chromiu8WWexperimental_push_constant", Extension::kUndefined},
+    {"chromium_experiMental_push_costanxx", Extension::kUndefined},
+    {"fgg", Extension::kUndefined},
+    {"X", Extension::kUndefined},
+    {"316", Extension::kUndefined},
 };
 
 using ExtensionParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/ast/interpolate_attribute_bench.cc b/src/tint/ast/interpolate_attribute_bench.cc
new file mode 100644
index 0000000..6382139
--- /dev/null
+++ b/src/tint/ast/interpolate_attribute_bench.cc
@@ -0,0 +1,67 @@
+// 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/interpolate_attribute_bench.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/ast/interpolate_attribute.h"
+
+#include <array>
+
+#include "benchmark/benchmark.h"
+
+namespace tint::ast {
+namespace {
+
+void InterpolationTypeParser(::benchmark::State& state) {
+    std::array kStrings{
+        "ccat",         "3",           "fVat",        "flat",        "1lat",
+        "fqaJ",         "flla77",      "lippeHHr",    "cin",         "lbGea",
+        "linear",       "liveaii",     "liWWe8r",     "xxiner",      "perggpctXve",
+        "ueVspXtve",    "3erspective", "perspective", "perspectivE", "peTTspectiPe",
+        "pxxdrspectve",
+    };
+    for (auto _ : state) {
+        for (auto& str : kStrings) {
+            auto result = ParseInterpolationType(str);
+            benchmark::DoNotOptimize(result);
+        }
+    }
+}
+
+BENCHMARK(InterpolationTypeParser);
+
+void InterpolationSamplingParser(::benchmark::State& state) {
+    std::array kStrings{
+        "44enter", "cSSVVter",     "centRr",     "center",   "ent9r",  "cente",   "VentORr",
+        "cenyroi", "77errtrllnid", "04entroid",  "centroid", "enooid", "centzzd", "ceiippr1i",
+        "saXXple", "55IImpnn99",   "aHHrrmplSS", "sample",   "kkle",   "jagRR",   "smbe",
+    };
+    for (auto _ : state) {
+        for (auto& str : kStrings) {
+            auto result = ParseInterpolationSampling(str);
+            benchmark::DoNotOptimize(result);
+        }
+    }
+}
+
+BENCHMARK(InterpolationSamplingParser);
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/interpolate_attribute_bench.cc.tmpl b/src/tint/ast/interpolate_attribute_bench.cc.tmpl
new file mode 100644
index 0000000..b75fe44
--- /dev/null
+++ b/src/tint/ast/interpolate_attribute_bench.cc.tmpl
@@ -0,0 +1,27 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate interpolate_attribute_bench.cc
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+
+#include "src/tint/ast/interpolate_attribute.h"
+
+#include <array>
+
+#include "benchmark/benchmark.h"
+
+namespace tint::ast {
+namespace {
+
+{{ Eval "BenchmarkParseEnum" (Sem.Enum "interpolation_type")}}
+
+{{ Eval "BenchmarkParseEnum" (Sem.Enum "interpolation_sampling")}}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/interpolate_attribute_test.cc b/src/tint/ast/interpolate_attribute_test.cc
index d8b6601..6c1204f 100644
--- a/src/tint/ast/interpolate_attribute_test.cc
+++ b/src/tint/ast/interpolate_attribute_test.cc
@@ -12,9 +12,20 @@
 // 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/ast/interpolate_attribute_test.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
 #include "src/tint/ast/interpolate_attribute.h"
 
+#include <string>
+
 #include "src/tint/ast/test_helper.h"
+#include "src/tint/utils/string.h"
 
 namespace tint::ast {
 namespace {
@@ -28,5 +39,117 @@
     EXPECT_EQ(InterpolationSampling::kCenter, d->sampling);
 }
 
+namespace interpolation_type_tests {
+
+namespace parse_print_tests {
+
+struct Case {
+    const char* string;
+    InterpolationType value;
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+    return out << "'" << std::string(c.string) << "'";
+}
+
+static constexpr Case kValidCases[] = {
+    {"flat", InterpolationType::kFlat},
+    {"linear", InterpolationType::kLinear},
+    {"perspective", InterpolationType::kPerspective},
+};
+
+static constexpr Case kInvalidCases[] = {
+    {"ccat", InterpolationType::kUndefined},          {"3", InterpolationType::kUndefined},
+    {"fVat", InterpolationType::kUndefined},          {"1inear", InterpolationType::kUndefined},
+    {"lnqqar", InterpolationType::kUndefined},        {"linell77", InterpolationType::kUndefined},
+    {"perppHqective", InterpolationType::kUndefined}, {"cespctve", InterpolationType::kUndefined},
+    {"ebGpective", InterpolationType::kUndefined},
+};
+
+using InterpolationTypeParseTest = testing::TestWithParam<Case>;
+
+TEST_P(InterpolationTypeParseTest, Parse) {
+    const char* string = GetParam().string;
+    InterpolationType expect = GetParam().value;
+    EXPECT_EQ(expect, ParseInterpolationType(string));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, InterpolationTypeParseTest, testing::ValuesIn(kValidCases));
+INSTANTIATE_TEST_SUITE_P(InvalidCases,
+                         InterpolationTypeParseTest,
+                         testing::ValuesIn(kInvalidCases));
+
+using InterpolationTypePrintTest = testing::TestWithParam<Case>;
+
+TEST_P(InterpolationTypePrintTest, Print) {
+    InterpolationType value = GetParam().value;
+    const char* expect = GetParam().string;
+    EXPECT_EQ(expect, utils::ToString(value));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, InterpolationTypePrintTest, testing::ValuesIn(kValidCases));
+
+}  // namespace parse_print_tests
+
+}  // namespace interpolation_type_tests
+
+namespace interpolation_sampling_tests {
+
+namespace parse_print_tests {
+
+struct Case {
+    const char* string;
+    InterpolationSampling value;
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+    return out << "'" << std::string(c.string) << "'";
+}
+
+static constexpr Case kValidCases[] = {
+    {"center", InterpolationSampling::kCenter},
+    {"centroid", InterpolationSampling::kCentroid},
+    {"sample", InterpolationSampling::kSample},
+};
+
+static constexpr Case kInvalidCases[] = {
+    {"cevteii", InterpolationSampling::kUndefined}, {"ceWWt8r", InterpolationSampling::kUndefined},
+    {"xxentr", InterpolationSampling::kUndefined},  {"ceXggrid", InterpolationSampling::kUndefined},
+    {"ceXriu", InterpolationSampling::kUndefined},  {"centr3id", InterpolationSampling::kUndefined},
+    {"sEmple", InterpolationSampling::kUndefined},  {"amTTlPP", InterpolationSampling::kUndefined},
+    {"ddamxxl", InterpolationSampling::kUndefined},
+};
+
+using InterpolationSamplingParseTest = testing::TestWithParam<Case>;
+
+TEST_P(InterpolationSamplingParseTest, Parse) {
+    const char* string = GetParam().string;
+    InterpolationSampling expect = GetParam().value;
+    EXPECT_EQ(expect, ParseInterpolationSampling(string));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases,
+                         InterpolationSamplingParseTest,
+                         testing::ValuesIn(kValidCases));
+INSTANTIATE_TEST_SUITE_P(InvalidCases,
+                         InterpolationSamplingParseTest,
+                         testing::ValuesIn(kInvalidCases));
+
+using InterpolationSamplingPrintTest = testing::TestWithParam<Case>;
+
+TEST_P(InterpolationSamplingPrintTest, Print) {
+    InterpolationSampling value = GetParam().value;
+    const char* expect = GetParam().string;
+    EXPECT_EQ(expect, utils::ToString(value));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases,
+                         InterpolationSamplingPrintTest,
+                         testing::ValuesIn(kValidCases));
+
+}  // namespace parse_print_tests
+
+}  // namespace interpolation_sampling_tests
+
 }  // namespace
 }  // namespace tint::ast
diff --git a/src/tint/ast/interpolate_attribute_test.cc.tmpl b/src/tint/ast/interpolate_attribute_test.cc.tmpl
new file mode 100644
index 0000000..1adc9ab
--- /dev/null
+++ b/src/tint/ast/interpolate_attribute_test.cc.tmpl
@@ -0,0 +1,45 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate interpolate_attribute_test.cc
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+
+#include "src/tint/ast/interpolate_attribute.h"
+
+#include <string>
+
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint::ast {
+namespace {
+
+using InterpolateAttributeTest = TestHelper;
+
+TEST_F(InterpolateAttributeTest, Creation) {
+    auto* d =
+        create<InterpolateAttribute>(InterpolationType::kLinear, InterpolationSampling::kCenter);
+    EXPECT_EQ(InterpolationType::kLinear, d->type);
+    EXPECT_EQ(InterpolationSampling::kCenter, d->sampling);
+}
+
+namespace interpolation_type_tests {
+
+{{ Eval "TestParsePrintEnum" (Sem.Enum "interpolation_type")}}
+
+}  // namespace interpolation_type_tests
+
+namespace interpolation_sampling_tests {
+
+{{ Eval "TestParsePrintEnum" (Sem.Enum "interpolation_sampling")}}
+
+}  // namespace interpolation_sampling_tests
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/fuzzers/tint_common_fuzzer.cc b/src/tint/fuzzers/tint_common_fuzzer.cc
index 1c24f35..15ab7d8 100644
--- a/src/tint/fuzzers/tint_common_fuzzer.cc
+++ b/src/tint/fuzzers/tint_common_fuzzer.cc
@@ -209,11 +209,11 @@
                                    "transformed into an invalid output program");
                 }
             }
+            return 0;
         }
 
         program = std::move(out.program);
         RunInspector(&program);
-
         return 1;
     };
 
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
index b641f53..25cde92 100644
--- a/src/tint/inspector/inspector.cc
+++ b/src/tint/inspector/inspector.cc
@@ -648,9 +648,9 @@
     if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) {
         // Recurse into members.
         for (auto* member : struct_ty->Members()) {
-            AddEntryPointInOutVariables(
-                name + "." + program_->Symbols().NameFor(member->Declaration()->symbol),
-                member->Type(), member->Declaration()->attributes, member->Location(), variables);
+            AddEntryPointInOutVariables(name + "." + program_->Symbols().NameFor(member->Name()),
+                                        member->Type(), member->Declaration()->attributes,
+                                        member->Location(), variables);
         }
         return;
     }
diff --git a/src/tint/intrinsics.def b/src/tint/intrinsics.def
index fb75c97..179cf88 100644
--- a/src/tint/intrinsics.def
+++ b/src/tint/intrinsics.def
@@ -51,6 +51,9 @@
   chromium_disable_uniformity_analysis
   // A Chromium-specific extension for push constants
   chromium_experimental_push_constant
+  // A Chromium-specific extension that enables passing of uniform, storage and workgroup 
+  // address-spaced pointers as parameters, as well as pointers into sub-objects.
+  chromium_experimental_full_ptr_parameters
 }
 
 // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
@@ -108,6 +111,22 @@
   sample
 }
 
+// https://www.w3.org/TR/WGSL/#vector-types
+enum type_alias {
+  vec2f
+  vec2h
+  vec2i
+  vec2u
+  vec3f
+  vec3h
+  vec3i
+  vec3u
+  vec4f
+  vec4h
+  vec4i
+  vec4u
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // WGSL primitive types                                                       //
 // Types may be decorated with @precedence(N) to prioritize which type        //
@@ -932,10 +951,10 @@
 @const op / <T: fia_fiu32_f16, N: num> (vec<N, T>, T) -> vec<N, T>
 @const op / <T: fia_fiu32_f16, N: num> (T, vec<N, T>) -> vec<N, T>
 
-op % <T: fiu32_f16>(T, T) -> T
-op % <T: fiu32_f16, N: num> (vec<N, T>, vec<N, T>) -> vec<N, T>
-op % <T: fiu32_f16, N: num> (vec<N, T>, T) -> vec<N, T>
-op % <T: fiu32_f16, N: num> (T, vec<N, T>) -> vec<N, T>
+@const op % <T: fia_fiu32_f16>(T, T) -> T
+@const op % <T: fia_fiu32_f16, N: num> (vec<N, T>, vec<N, T>) -> vec<N, T>
+@const op % <T: fia_fiu32_f16, N: num> (vec<N, T>, T) -> vec<N, T>
+@const op % <T: fia_fiu32_f16, N: num> (T, vec<N, T>) -> vec<N, T>
 
 @const op ^ <T: ia_iu32>(T, T) -> T
 @const op ^ <T: ia_iu32, N: num> (vec<N, T>, vec<N, T>) -> vec<N, T>
@@ -971,10 +990,8 @@
 @const op >= <T: fia_fiu32_f16>(T, T) -> bool
 @const op >= <T: fiu32_f16, N: num> (vec<N, T>, vec<N, T>) -> vec<N, bool>
 
-@const op << <T: iu32>(T, u32) -> T
-@const op << <T: iu32, N: num> (vec<N, T>, vec<N, u32>) -> vec<N, T>
-@const op << <T: ia>(T, u32) -> T
-@const op << <T: ia, N: num> (vec<N, T>, vec<N, u32>) -> vec<N, T>
+@const op << <T: ia_iu32>(T, u32) -> T
+@const op << <T: ia_iu32, N: num> (vec<N, T>, vec<N, u32>) -> vec<N, T>
 
 op >> <T: iu32>(T, u32) -> T
 op >> <T: iu32, N: num> (vec<N, T>, vec<N, u32>) -> vec<N, T>
diff --git a/src/tint/ir/binary.cc b/src/tint/ir/binary.cc
new file mode 100644
index 0000000..1a35b61
--- /dev/null
+++ b/src/tint/ir/binary.cc
@@ -0,0 +1,97 @@
+// 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.
+
+#include "src/tint/ir/binary.h"
+#include "src/tint/debug.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Binary);
+
+namespace tint::ir {
+
+Binary::Binary(Kind kind, const Value* result, const Value* lhs, const Value* rhs)
+    : kind_(kind), result_(result), lhs_(lhs), rhs_(rhs) {
+    TINT_ASSERT(IR, result_);
+    TINT_ASSERT(IR, lhs_);
+    TINT_ASSERT(IR, rhs_);
+}
+
+Binary::~Binary() = default;
+
+std::ostream& Binary::ToString(std::ostream& out) const {
+    Result()->ToString(out) << " = ";
+    lhs_->ToString(out) << " ";
+
+    switch (GetKind()) {
+        case Binary::Kind::kAdd:
+            out << "+";
+            break;
+        case Binary::Kind::kSubtract:
+            out << "-";
+            break;
+        case Binary::Kind::kMultiply:
+            out << "*";
+            break;
+        case Binary::Kind::kDivide:
+            out << "/";
+            break;
+        case Binary::Kind::kModulo:
+            out << "%";
+            break;
+        case Binary::Kind::kAnd:
+            out << "&";
+            break;
+        case Binary::Kind::kOr:
+            out << "|";
+            break;
+        case Binary::Kind::kXor:
+            out << "^";
+            break;
+        case Binary::Kind::kLogicalAnd:
+            out << "&&";
+            break;
+        case Binary::Kind::kLogicalOr:
+            out << "||";
+            break;
+        case Binary::Kind::kEqual:
+            out << "==";
+            break;
+        case Binary::Kind::kNotEqual:
+            out << "!=";
+            break;
+        case Binary::Kind::kLessThan:
+            out << "<";
+            break;
+        case Binary::Kind::kGreaterThan:
+            out << ">";
+            break;
+        case Binary::Kind::kLessThanEqual:
+            out << "<=";
+            break;
+        case Binary::Kind::kGreaterThanEqual:
+            out << ">=";
+            break;
+        case Binary::Kind::kShiftLeft:
+            out << "<<";
+            break;
+        case Binary::Kind::kShiftRight:
+            out << ">>";
+            break;
+    }
+    out << " ";
+    rhs_->ToString(out);
+
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/binary.h b/src/tint/ir/binary.h
new file mode 100644
index 0000000..755dd9c
--- /dev/null
+++ b/src/tint/ir/binary.h
@@ -0,0 +1,96 @@
+// 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_IR_BINARY_H_
+#define SRC_TINT_IR_BINARY_H_
+
+#include <ostream>
+
+#include "src/tint/castable.h"
+#include "src/tint/ir/instruction.h"
+#include "src/tint/ir/value.h"
+
+namespace tint::ir {
+
+/// An instruction in the IR.
+class Binary : public Castable<Binary, Instruction> {
+  public:
+    /// The kind of instruction.
+    enum class Kind {
+        kAdd,
+        kSubtract,
+        kMultiply,
+        kDivide,
+        kModulo,
+
+        kAnd,
+        kOr,
+        kXor,
+
+        kLogicalAnd,
+        kLogicalOr,
+
+        kEqual,
+        kNotEqual,
+        kLessThan,
+        kGreaterThan,
+        kLessThanEqual,
+        kGreaterThanEqual,
+
+        kShiftLeft,
+        kShiftRight
+    };
+
+    /// Constructor
+    /// @param kind the kind of binary instruction
+    /// @param result the result value
+    /// @param lhs the lhs of the instruction
+    /// @param rhs the rhs of the instruction
+    Binary(Kind kind, const Value* result, const Value* lhs, const Value* rhs);
+    Binary(const Binary& instr) = delete;
+    Binary(Binary&& instr) = delete;
+    ~Binary() override;
+
+    Binary& operator=(const Binary& instr) = delete;
+    Binary& operator=(Binary&& instr) = delete;
+
+    /// @returns the kind of instruction
+    Kind GetKind() const { return kind_; }
+
+    /// @returns the result value for the instruction
+    const Value* Result() const { return result_; }
+
+    /// @returns the left-hand-side value for the instruction
+    const Value* LHS() const { return lhs_; }
+
+    /// @returns the right-hand-side value for the instruction
+    const Value* RHS() const { return rhs_; }
+
+    /// Write the instruction to the given stream
+    /// @param out the stream to write to
+    /// @returns the stream
+    std::ostream& ToString(std::ostream& out) const override;
+
+  private:
+    Kind kind_;
+    const Value* result_ = nullptr;
+    const Value* lhs_ = nullptr;
+    const Value* rhs_ = nullptr;
+};
+
+std::ostream& operator<<(std::ostream& out, const Binary&);
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_BINARY_H_
diff --git a/src/tint/ir/binary_test.cc b/src/tint/ir/binary_test.cc
new file mode 100644
index 0000000..81d0ada
--- /dev/null
+++ b/src/tint/ir/binary_test.cc
@@ -0,0 +1,499 @@
+// 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.
+
+#include <sstream>
+
+#include "src/tint/ir/instruction.h"
+#include "src/tint/ir/test_helper.h"
+
+namespace tint::ir {
+namespace {
+
+using IR_InstructionTest = TestHelper;
+
+TEST_F(IR_InstructionTest, CreateAnd) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.And(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kAnd);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 & 2");
+}
+
+TEST_F(IR_InstructionTest, CreateOr) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Or(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kOr);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 | 2");
+}
+
+TEST_F(IR_InstructionTest, CreateXor) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Xor(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kXor);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 ^ 2");
+}
+
+TEST_F(IR_InstructionTest, CreateLogicalAnd) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.LogicalAnd(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kLogicalAnd);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 && 2");
+}
+
+TEST_F(IR_InstructionTest, CreateLogicalOr) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.LogicalOr(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kLogicalOr);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 || 2");
+}
+
+TEST_F(IR_InstructionTest, CreateEqual) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Equal(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kEqual);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 == 2");
+}
+
+TEST_F(IR_InstructionTest, CreateNotEqual) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.NotEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kNotEqual);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 != 2");
+}
+
+TEST_F(IR_InstructionTest, CreateLessThan) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.LessThan(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kLessThan);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 < 2");
+}
+
+TEST_F(IR_InstructionTest, CreateGreaterThan) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.GreaterThan(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kGreaterThan);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 > 2");
+}
+
+TEST_F(IR_InstructionTest, CreateLessThanEqual) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.LessThanEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kLessThanEqual);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 <= 2");
+}
+
+TEST_F(IR_InstructionTest, CreateGreaterThanEqual) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.GreaterThanEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kGreaterThanEqual);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 >= 2");
+}
+
+TEST_F(IR_InstructionTest, CreateShiftLeft) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.ShiftLeft(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kShiftLeft);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 << 2");
+}
+
+TEST_F(IR_InstructionTest, CreateShiftRight) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.ShiftRight(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kShiftRight);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 >> 2");
+}
+
+TEST_F(IR_InstructionTest, CreateAdd) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Add(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kAdd);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 + 2");
+}
+
+TEST_F(IR_InstructionTest, CreateSubtract) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Subtract(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kSubtract);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 - 2");
+}
+
+TEST_F(IR_InstructionTest, CreateMultiply) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Multiply(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kMultiply);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 * 2");
+}
+
+TEST_F(IR_InstructionTest, CreateDivide) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Divide(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kDivide);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 / 2");
+}
+
+TEST_F(IR_InstructionTest, CreateModulo) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Modulo(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kModulo);
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
+
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
+
+    std::stringstream str;
+    instr->ToString(str);
+    EXPECT_EQ(str.str(), "%42 = 4 % 2");
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 5c55b91..0f0200d 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -97,82 +97,80 @@
     return next_temp_id++;
 }
 
-const Instruction* Builder::CreateInstruction(Instruction::Kind kind,
-                                              const Value* lhs,
-                                              const Value* rhs) {
-    return ir.instructions.Create<ir::Instruction>(kind, Temp(), lhs, rhs);
+const Binary* Builder::CreateBinary(Binary::Kind kind, const Value* lhs, const Value* rhs) {
+    return ir.instructions.Create<ir::Binary>(kind, Temp(), lhs, rhs);
 }
 
-const Instruction* Builder::And(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kAnd, lhs, rhs);
+const Binary* Builder::And(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kAnd, lhs, rhs);
 }
 
-const Instruction* Builder::Or(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kOr, lhs, rhs);
+const Binary* Builder::Or(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kOr, lhs, rhs);
 }
 
-const Instruction* Builder::Xor(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kXor, lhs, rhs);
+const Binary* Builder::Xor(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kXor, lhs, rhs);
 }
 
-const Instruction* Builder::LogicalAnd(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kLogicalAnd, lhs, rhs);
+const Binary* Builder::LogicalAnd(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kLogicalAnd, lhs, rhs);
 }
 
-const Instruction* Builder::LogicalOr(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kLogicalOr, lhs, rhs);
+const Binary* Builder::LogicalOr(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kLogicalOr, lhs, rhs);
 }
 
-const Instruction* Builder::Equal(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kEqual, lhs, rhs);
+const Binary* Builder::Equal(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kEqual, lhs, rhs);
 }
 
-const Instruction* Builder::NotEqual(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kNotEqual, lhs, rhs);
+const Binary* Builder::NotEqual(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kNotEqual, lhs, rhs);
 }
 
-const Instruction* Builder::LessThan(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kLessThan, lhs, rhs);
+const Binary* Builder::LessThan(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kLessThan, lhs, rhs);
 }
 
-const Instruction* Builder::GreaterThan(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kGreaterThan, lhs, rhs);
+const Binary* Builder::GreaterThan(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kGreaterThan, lhs, rhs);
 }
 
-const Instruction* Builder::LessThanEqual(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kLessThanEqual, lhs, rhs);
+const Binary* Builder::LessThanEqual(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kLessThanEqual, lhs, rhs);
 }
 
-const Instruction* Builder::GreaterThanEqual(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kGreaterThanEqual, lhs, rhs);
+const Binary* Builder::GreaterThanEqual(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kGreaterThanEqual, lhs, rhs);
 }
 
-const Instruction* Builder::ShiftLeft(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kShiftLeft, lhs, rhs);
+const Binary* Builder::ShiftLeft(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kShiftLeft, lhs, rhs);
 }
 
-const Instruction* Builder::ShiftRight(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kShiftRight, lhs, rhs);
+const Binary* Builder::ShiftRight(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kShiftRight, lhs, rhs);
 }
 
-const Instruction* Builder::Add(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kAdd, lhs, rhs);
+const Binary* Builder::Add(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kAdd, lhs, rhs);
 }
 
-const Instruction* Builder::Subtract(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kSubtract, lhs, rhs);
+const Binary* Builder::Subtract(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kSubtract, lhs, rhs);
 }
 
-const Instruction* Builder::Multiply(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kMultiply, lhs, rhs);
+const Binary* Builder::Multiply(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kMultiply, lhs, rhs);
 }
 
-const Instruction* Builder::Divide(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kDivide, lhs, rhs);
+const Binary* Builder::Divide(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kDivide, lhs, rhs);
 }
 
-const Instruction* Builder::Modulo(const Value* lhs, const Value* rhs) {
-    return CreateInstruction(Instruction::Kind::kModulo, lhs, rhs);
+const Binary* Builder::Modulo(const Value* lhs, const Value* rhs) {
+    return CreateBinary(Binary::Kind::kModulo, lhs, rhs);
 }
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index d5065b8..3f2e011 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -15,10 +15,10 @@
 #ifndef SRC_TINT_IR_BUILDER_H_
 #define SRC_TINT_IR_BUILDER_H_
 
+#include "src/tint/ir/binary.h"
 #include "src/tint/ir/constant.h"
 #include "src/tint/ir/function.h"
 #include "src/tint/ir/if.h"
-#include "src/tint/ir/instruction.h"
 #include "src/tint/ir/loop.h"
 #include "src/tint/ir/module.h"
 #include "src/tint/ir/switch.h"
@@ -102,117 +102,115 @@
     /// @param lhs the left-hand-side of the operation
     /// @param rhs the right-hand-side of the operation
     /// @returns the operation
-    const Instruction* CreateInstruction(Instruction::Kind kind,
-                                         const Value* lhs,
-                                         const Value* rhs);
+    const Binary* CreateBinary(Binary::Kind kind, const Value* lhs, const Value* rhs);
 
     /// Creates an And operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* And(const Value* lhs, const Value* rhs);
+    const Binary* And(const Value* lhs, const Value* rhs);
 
     /// Creates an Or operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* Or(const Value* lhs, const Value* rhs);
+    const Binary* Or(const Value* lhs, const Value* rhs);
 
     /// Creates an Xor operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* Xor(const Value* lhs, const Value* rhs);
+    const Binary* Xor(const Value* lhs, const Value* rhs);
 
     /// Creates an LogicalAnd operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* LogicalAnd(const Value* lhs, const Value* rhs);
+    const Binary* LogicalAnd(const Value* lhs, const Value* rhs);
 
     /// Creates an LogicalOr operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* LogicalOr(const Value* lhs, const Value* rhs);
+    const Binary* LogicalOr(const Value* lhs, const Value* rhs);
 
     /// Creates an Equal operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* Equal(const Value* lhs, const Value* rhs);
+    const Binary* Equal(const Value* lhs, const Value* rhs);
 
     /// Creates an NotEqual operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* NotEqual(const Value* lhs, const Value* rhs);
+    const Binary* NotEqual(const Value* lhs, const Value* rhs);
 
     /// Creates an LessThan operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* LessThan(const Value* lhs, const Value* rhs);
+    const Binary* LessThan(const Value* lhs, const Value* rhs);
 
     /// Creates an GreaterThan operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* GreaterThan(const Value* lhs, const Value* rhs);
+    const Binary* GreaterThan(const Value* lhs, const Value* rhs);
 
     /// Creates an LessThanEqual operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* LessThanEqual(const Value* lhs, const Value* rhs);
+    const Binary* LessThanEqual(const Value* lhs, const Value* rhs);
 
     /// Creates an GreaterThanEqual operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* GreaterThanEqual(const Value* lhs, const Value* rhs);
+    const Binary* GreaterThanEqual(const Value* lhs, const Value* rhs);
 
     /// Creates an ShiftLeft operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* ShiftLeft(const Value* lhs, const Value* rhs);
+    const Binary* ShiftLeft(const Value* lhs, const Value* rhs);
 
     /// Creates an ShiftRight operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* ShiftRight(const Value* lhs, const Value* rhs);
+    const Binary* ShiftRight(const Value* lhs, const Value* rhs);
 
     /// Creates an Add operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* Add(const Value* lhs, const Value* rhs);
+    const Binary* Add(const Value* lhs, const Value* rhs);
 
     /// Creates an Subtract operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* Subtract(const Value* lhs, const Value* rhs);
+    const Binary* Subtract(const Value* lhs, const Value* rhs);
 
     /// Creates an Multiply operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* Multiply(const Value* lhs, const Value* rhs);
+    const Binary* Multiply(const Value* lhs, const Value* rhs);
 
     /// Creates an Divide operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* Divide(const Value* lhs, const Value* rhs);
+    const Binary* Divide(const Value* lhs, const Value* rhs);
 
     /// Creates an Modulo operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Instruction* Modulo(const Value* lhs, const Value* rhs);
+    const Binary* Modulo(const Value* lhs, const Value* rhs);
 
     /// @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 2286bf4..042a7d5 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -562,7 +562,7 @@
         return utils::Failure;
     }
 
-    const Instruction* instr = nullptr;
+    const Binary* instr = nullptr;
     switch (expr->op) {
         case ast::BinaryOp::kAnd:
             instr = builder.And(lhs.Get(), rhs.Get());
diff --git a/src/tint/ir/constant.cc b/src/tint/ir/constant.cc
index 78895bf..0138190 100644
--- a/src/tint/ir/constant.cc
+++ b/src/tint/ir/constant.cc
@@ -32,22 +32,22 @@
 
 Constant::~Constant() = default;
 
-std::ostream& operator<<(std::ostream& out, const Constant& r) {
-    switch (r.GetKind()) {
+std::ostream& Constant::ToString(std::ostream& out) const {
+    switch (GetKind()) {
         case Constant::Kind::kF32:
-            out << std::to_string(r.AsF32().value);
+            out << std::to_string(AsF32().value);
             break;
         case Constant::Kind::kF16:
-            out << std::to_string(r.AsF16().value);
+            out << std::to_string(AsF16().value);
             break;
         case Constant::Kind::kI32:
-            out << std::to_string(r.AsI32().value);
+            out << std::to_string(AsI32().value);
             break;
         case Constant::Kind::kU32:
-            out << std::to_string(r.AsU32().value);
+            out << std::to_string(AsU32().value);
             break;
         case Constant::Kind::kBool:
-            out << (r.AsBool() ? "true" : "false");
+            out << (AsBool() ? "true" : "false");
             break;
     }
     return out;
diff --git a/src/tint/ir/constant.h b/src/tint/ir/constant.h
index c06e121..23eeb02 100644
--- a/src/tint/ir/constant.h
+++ b/src/tint/ir/constant.h
@@ -100,6 +100,11 @@
     /// @note, must only be called if `IsBool()` is true
     bool AsBool() const { return std::get<bool>(data_); }
 
+    /// Write the constant to the given stream
+    /// @param out the stream to write to
+    /// @returns the stream
+    std::ostream& ToString(std::ostream& out) const override;
+
   private:
     /// The type of data stored in this constant
     Kind kind_;
@@ -107,8 +112,6 @@
     std::variant<f32, f16, u32, i32, bool> data_;
 };
 
-std::ostream& operator<<(std::ostream& out, const Constant& r);
-
 }  // namespace tint::ir
 
 #endif  // SRC_TINT_IR_CONSTANT_H_
diff --git a/src/tint/ir/constant_test.cc b/src/tint/ir/constant_test.cc
index 7b4e224..b20b424 100644
--- a/src/tint/ir/constant_test.cc
+++ b/src/tint/ir/constant_test.cc
@@ -32,7 +32,7 @@
     auto* val = b.builder.Constant(1.2_f);
     EXPECT_EQ(1.2_f, val->AsF32());
 
-    str << *val;
+    val->ToString(str);
     EXPECT_EQ("1.200000", str.str());
 
     EXPECT_TRUE(val->IsF32());
@@ -50,7 +50,7 @@
     auto* val = b.builder.Constant(1.1_h);
     EXPECT_EQ(1.1_h, val->AsF16());
 
-    str << *val;
+    val->ToString(str);
     EXPECT_EQ("1.099609", str.str());
 
     EXPECT_FALSE(val->IsF32());
@@ -68,7 +68,7 @@
     auto* val = b.builder.Constant(1_i);
     EXPECT_EQ(1_i, val->AsI32());
 
-    str << *val;
+    val->ToString(str);
     EXPECT_EQ("1", str.str());
 
     EXPECT_FALSE(val->IsF32());
@@ -86,7 +86,7 @@
     auto* val = b.builder.Constant(2_u);
     EXPECT_EQ(2_u, val->AsU32());
 
-    str << *val;
+    val->ToString(str);
     EXPECT_EQ("2", str.str());
 
     EXPECT_FALSE(val->IsF32());
@@ -104,14 +104,14 @@
     auto* val = b.builder.Constant(false);
     EXPECT_FALSE(val->AsBool());
 
-    str << *val;
+    val->ToString(str);
     EXPECT_EQ("false", str.str());
 
     str.str("");
     val = b.builder.Constant(true);
     EXPECT_TRUE(val->AsBool());
 
-    str << *val;
+    val->ToString(str);
     EXPECT_EQ("true", str.str());
 
     EXPECT_FALSE(val->IsF32());
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index e2749a1..d8ee4ba 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -63,7 +63,7 @@
 
 void Disassembler::EmitBlockInstructions(const Block* b) {
     for (const auto* instr : b->instructions) {
-        out_ << *instr << std::endl;
+        instr->ToString(out_) << std::endl;
     }
 }
 
diff --git a/src/tint/ir/instruction.cc b/src/tint/ir/instruction.cc
index 6c0f176..e54b13f 100644
--- a/src/tint/ir/instruction.cc
+++ b/src/tint/ir/instruction.cc
@@ -18,90 +18,8 @@
 
 namespace tint::ir {
 
-Instruction::Instruction() {}
-
-Instruction::Instruction(Kind kind, const Value* result, const Value* lhs, const Value* rhs)
-    : kind_(kind), result_(result), args_({lhs, rhs}) {}
-
-Instruction::Instruction(const Instruction&) = default;
-
-Instruction::Instruction(Instruction&& instr) = default;
+Instruction::Instruction() = default;
 
 Instruction::~Instruction() = default;
 
-Instruction& Instruction::operator=(const Instruction& instr) = default;
-
-Instruction& Instruction::operator=(Instruction&& instr) = default;
-
-std::ostream& operator<<(std::ostream& out, const Instruction& instr) {
-    out << *(instr.Result()) << " = ";
-    if (instr.HasLHS()) {
-        out << *(instr.LHS());
-    }
-    out << " ";
-
-    switch (instr.GetKind()) {
-        case Instruction::Kind::kAdd:
-            out << "+";
-            break;
-        case Instruction::Kind::kSubtract:
-            out << "-";
-            break;
-        case Instruction::Kind::kMultiply:
-            out << "*";
-            break;
-        case Instruction::Kind::kDivide:
-            out << "/";
-            break;
-        case Instruction::Kind::kModulo:
-            out << "%";
-            break;
-        case Instruction::Kind::kAnd:
-            out << "&";
-            break;
-        case Instruction::Kind::kOr:
-            out << "|";
-            break;
-        case Instruction::Kind::kXor:
-            out << "^";
-            break;
-        case Instruction::Kind::kLogicalAnd:
-            out << "&&";
-            break;
-        case Instruction::Kind::kLogicalOr:
-            out << "||";
-            break;
-        case Instruction::Kind::kEqual:
-            out << "==";
-            break;
-        case Instruction::Kind::kNotEqual:
-            out << "!=";
-            break;
-        case Instruction::Kind::kLessThan:
-            out << "<";
-            break;
-        case Instruction::Kind::kGreaterThan:
-            out << ">";
-            break;
-        case Instruction::Kind::kLessThanEqual:
-            out << "<=";
-            break;
-        case Instruction::Kind::kGreaterThanEqual:
-            out << ">=";
-            break;
-        case Instruction::Kind::kShiftLeft:
-            out << "<<";
-            break;
-        case Instruction::Kind::kShiftRight:
-            out << ">>";
-            break;
-    }
-
-    if (instr.HasRHS()) {
-        out << " " << *(instr.RHS());
-    }
-
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/instruction.h b/src/tint/ir/instruction.h
index 461b1f7..8b68465 100644
--- a/src/tint/ir/instruction.h
+++ b/src/tint/ir/instruction.h
@@ -18,98 +18,30 @@
 #include <ostream>
 
 #include "src/tint/castable.h"
-#include "src/tint/debug.h"
-#include "src/tint/ir/value.h"
-#include "src/tint/utils/vector.h"
 
 namespace tint::ir {
 
 /// An instruction in the IR.
 class Instruction : public Castable<Instruction> {
   public:
-    /// The kind of instruction.
-    enum class Kind {
-        kAdd,
-        kSubtract,
-        kMultiply,
-        kDivide,
-        kModulo,
+    Instruction(const Instruction& instr) = delete;
+    Instruction(Instruction&& instr) = delete;
+    /// Destructor
+    ~Instruction() override;
 
-        kAnd,
-        kOr,
-        kXor,
+    Instruction& operator=(const Instruction& instr) = delete;
+    Instruction& operator=(Instruction&& instr) = delete;
 
-        kLogicalAnd,
-        kLogicalOr,
+    /// Write the instruction to the given stream
+    /// @param out the stream to write to
+    /// @returns the stream
+    virtual std::ostream& ToString(std::ostream& out) const = 0;
 
-        kEqual,
-        kNotEqual,
-        kLessThan,
-        kGreaterThan,
-        kLessThanEqual,
-        kGreaterThanEqual,
-
-        kShiftLeft,
-        kShiftRight
-    };
-
+  protected:
     /// Constructor
     Instruction();
-    /// Constructor
-    /// @param kind the kind of instruction
-    /// @param result the result value
-    /// @param lhs the lhs of the instruction
-    /// @param rhs the rhs of the instruction
-    Instruction(Kind kind, const Value* result, const Value* lhs, const Value* rhs);
-    /// Copy constructor
-    /// @param instr the instruction to copy from
-    Instruction(const Instruction& instr);
-    /// Move constructor
-    /// @param instr the instruction to move from
-    Instruction(Instruction&& instr);
-    /// Destructor
-    ~Instruction();
-
-    /// Copy assign
-    /// @param instr the instruction to copy from
-    /// @returns a reference to this
-    Instruction& operator=(const Instruction& instr);
-    /// Move assign
-    /// @param instr the instruction to move from
-    /// @returns a reference to this
-    Instruction& operator=(Instruction&& instr);
-
-    /// @returns the kind of instruction
-    Kind GetKind() const { return kind_; }
-
-    /// @returns the result value for the instruction
-    const Value* Result() const { return result_; }
-
-    /// @returns true if the instruction has a LHS
-    bool HasLHS() const { return args_.Length() >= 1; }
-    /// @returns the left-hand-side value for the instruction
-    const Value* LHS() const {
-        TINT_ASSERT(IR, HasLHS());
-        return args_[0];
-    }
-
-    /// @returns true if the instruction has a RHS
-    bool HasRHS() const { return args_.Length() >= 2; }
-    /// @returns the right-hand-side value for the instruction
-    const Value* RHS() const {
-        TINT_ASSERT(IR, HasRHS());
-        return args_[1];
-    }
-
-  private:
-    Kind kind_;
-
-    const Value* result_;
-    utils::Vector<const Value*, 2> args_;
 };
 
-std::ostream& operator<<(std::ostream& out, const Instruction&);
-
 }  // namespace tint::ir
 
 #endif  // SRC_TINT_IR_INSTRUCTION_H_
diff --git a/src/tint/ir/instruction_test.cc b/src/tint/ir/instruction_test.cc
index 0957903..08302ed 100644
--- a/src/tint/ir/instruction_test.cc
+++ b/src/tint/ir/instruction_test.cc
@@ -20,9 +20,9 @@
 namespace tint::ir {
 namespace {
 
-using IR_InstructionTest = TestHelper;
+using IR_BinaryTest = TestHelper;
 
-TEST_F(IR_InstructionTest, CreateAnd) {
+TEST_F(IR_BinaryTest, CreateAnd) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -33,24 +33,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 & 2");
 }
 
-TEST_F(IR_InstructionTest, CreateOr) {
+TEST_F(IR_BinaryTest, CreateOr) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -61,24 +59,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 | 2");
 }
 
-TEST_F(IR_InstructionTest, CreateXor) {
+TEST_F(IR_BinaryTest, CreateXor) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -89,24 +85,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 ^ 2");
 }
 
-TEST_F(IR_InstructionTest, CreateLogicalAnd) {
+TEST_F(IR_BinaryTest, CreateLogicalAnd) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -118,24 +112,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 && 2");
 }
 
-TEST_F(IR_InstructionTest, CreateLogicalOr) {
+TEST_F(IR_BinaryTest, CreateLogicalOr) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -146,24 +138,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 || 2");
 }
 
-TEST_F(IR_InstructionTest, CreateEqual) {
+TEST_F(IR_BinaryTest, CreateEqual) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -174,24 +164,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 == 2");
 }
 
-TEST_F(IR_InstructionTest, CreateNotEqual) {
+TEST_F(IR_BinaryTest, CreateNotEqual) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -202,24 +190,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 != 2");
 }
 
-TEST_F(IR_InstructionTest, CreateLessThan) {
+TEST_F(IR_BinaryTest, CreateLessThan) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -230,24 +216,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 < 2");
 }
 
-TEST_F(IR_InstructionTest, CreateGreaterThan) {
+TEST_F(IR_BinaryTest, CreateGreaterThan) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -259,24 +243,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 > 2");
 }
 
-TEST_F(IR_InstructionTest, CreateLessThanEqual) {
+TEST_F(IR_BinaryTest, CreateLessThanEqual) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -288,24 +270,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 <= 2");
 }
 
-TEST_F(IR_InstructionTest, CreateGreaterThanEqual) {
+TEST_F(IR_BinaryTest, CreateGreaterThanEqual) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -317,24 +297,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 >= 2");
 }
 
-TEST_F(IR_InstructionTest, CreateShiftLeft) {
+TEST_F(IR_BinaryTest, CreateShiftLeft) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -345,24 +323,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 << 2");
 }
 
-TEST_F(IR_InstructionTest, CreateShiftRight) {
+TEST_F(IR_BinaryTest, CreateShiftRight) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -374,24 +350,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 >> 2");
 }
 
-TEST_F(IR_InstructionTest, CreateAdd) {
+TEST_F(IR_BinaryTest, CreateAdd) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -402,24 +376,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 + 2");
 }
 
-TEST_F(IR_InstructionTest, CreateSubtract) {
+TEST_F(IR_BinaryTest, CreateSubtract) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -430,24 +402,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 - 2");
 }
 
-TEST_F(IR_InstructionTest, CreateMultiply) {
+TEST_F(IR_BinaryTest, CreateMultiply) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -458,24 +428,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 * 2");
 }
 
-TEST_F(IR_InstructionTest, CreateDivide) {
+TEST_F(IR_BinaryTest, CreateDivide) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -486,24 +454,22 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 / 2");
 }
 
-TEST_F(IR_InstructionTest, CreateModulo) {
+TEST_F(IR_BinaryTest, CreateModulo) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
@@ -514,20 +480,18 @@
     ASSERT_TRUE(instr->Result()->Is<Temp>());
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr->HasLHS());
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
     auto lhs = instr->LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr->HasRHS());
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
     auto rhs = instr->RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << *instr;
+    instr->ToString(str);
     EXPECT_EQ(str.str(), "%42 = 4 % 2");
 }
 
diff --git a/src/tint/ir/temp.cc b/src/tint/ir/temp.cc
index de227ee..30efb86 100644
--- a/src/tint/ir/temp.cc
+++ b/src/tint/ir/temp.cc
@@ -24,8 +24,8 @@
 
 Temp::~Temp() = default;
 
-std::ostream& operator<<(std::ostream& out, const Temp& r) {
-    out << "%" << std::to_string(r.AsId());
+std::ostream& Temp::ToString(std::ostream& out) const {
+    out << "%" << std::to_string(AsId());
     return out;
 }
 
diff --git a/src/tint/ir/temp.h b/src/tint/ir/temp.h
index b16baf2..c621b5c 100644
--- a/src/tint/ir/temp.h
+++ b/src/tint/ir/temp.h
@@ -43,12 +43,15 @@
     /// @returns the value data as an `Id`.
     Id AsId() const { return id_; }
 
+    /// Write the temp to the given stream
+    /// @param out the stream to write to
+    /// @returns the stream
+    std::ostream& ToString(std::ostream& out) const override;
+
   private:
     Id id_ = 0;
 };
 
-std::ostream& operator<<(std::ostream& out, const Temp& r);
-
 }  // namespace tint::ir
 
 #endif  // SRC_TINT_IR_TEMP_H_
diff --git a/src/tint/ir/temp_test.cc b/src/tint/ir/temp_test.cc
index b5d817b..1dcae13 100644
--- a/src/tint/ir/temp_test.cc
+++ b/src/tint/ir/temp_test.cc
@@ -33,7 +33,7 @@
     auto* val = b.builder.Temp();
     EXPECT_EQ(4u, val->AsId());
 
-    str << *val;
+    val->ToString(str);
     EXPECT_EQ("%4", str.str());
 }
 
diff --git a/src/tint/ir/value.cc b/src/tint/ir/value.cc
index a1431e3..93040cf 100644
--- a/src/tint/ir/value.cc
+++ b/src/tint/ir/value.cc
@@ -25,17 +25,4 @@
 
 Value::~Value() = default;
 
-std::ostream& operator<<(std::ostream& out, const Value& v) {
-    const auto* ptr = &v;
-
-    if (auto* c = ptr->As<Constant>()) {
-        out << *c;
-    } else if (auto* t = ptr->As<Temp>()) {
-        out << *t;
-    } else {
-        out << "Unknown value";
-    }
-    return out;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/value.h b/src/tint/ir/value.h
index 326931f..9c41f52 100644
--- a/src/tint/ir/value.h
+++ b/src/tint/ir/value.h
@@ -25,7 +25,7 @@
 class Value : public Castable<Value> {
   public:
     /// Destructor
-    virtual ~Value();
+    ~Value() override;
 
     Value(const Value&) = delete;
     Value(Value&&) = delete;
@@ -33,13 +33,16 @@
     Value& operator=(const Value&) = delete;
     Value& operator=(Value&&) = delete;
 
+    /// Write the value to the given stream
+    /// @param out the stream to write to
+    /// @returns the stream
+    virtual std::ostream& ToString(std::ostream& out) const = 0;
+
   protected:
     /// Constructor
     Value();
 };
 
-std::ostream& operator<<(std::ostream& out, const Value& v);
-
 }  // namespace tint::ir
 
 #endif  // SRC_TINT_IR_VALUE_H_
diff --git a/src/tint/number.h b/src/tint/number.h
index dc66689..9b7ce43 100644
--- a/src/tint/number.h
+++ b/src/tint/number.h
@@ -543,6 +543,38 @@
     return result;
 }
 
+namespace detail {
+/// @returns the remainder of e1 / e2
+template <typename T>
+inline T Mod(T e1, T e2) {
+    return e1 - e2 * static_cast<T>(std::trunc(e1 / e2));
+}
+}  // namespace detail
+
+/// @returns the remainder of a / b, or an empty optional if the resulting value overflowed the AInt
+inline std::optional<AInt> CheckedMod(AInt a, AInt b) {
+    if (b == 0) {
+        return {};
+    }
+
+    if (b == -1 && a == AInt::Lowest()) {
+        return {};
+    }
+
+    return AInt{detail::Mod(a.value, b.value)};
+}
+
+/// @returns the remainder of a / b, or an empty optional if the resulting value overflowed the
+/// float value
+template <typename FloatingPointT, typename = traits::EnableIf<IsFloatingPoint<FloatingPointT>>>
+inline std::optional<FloatingPointT> CheckedMod(FloatingPointT a, FloatingPointT b) {
+    auto result = FloatingPointT{detail::Mod(a.value, b.value)};
+    if (!std::isfinite(result.value)) {
+        return {};
+    }
+    return result;
+}
+
 /// @returns a * b + c, or an empty optional if the value overflowed the AInt
 inline std::optional<AInt> CheckedMadd(AInt a, AInt b, AInt c) {
     // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80635
diff --git a/src/tint/number_test.cc b/src/tint/number_test.cc
index f21e531..040fd76 100644
--- a/src/tint/number_test.cc
+++ b/src/tint/number_test.cc
@@ -687,6 +687,58 @@
                                                   CheckedDivTest_FloatCases<f32>(),
                                                   CheckedDivTest_FloatCases<f16>())));
 
+using CheckedModTest_AInt = testing::TestWithParam<BinaryCheckedCase_AInt>;
+TEST_P(CheckedModTest_AInt, Test) {
+    auto expect = std::get<0>(GetParam());
+    auto a = std::get<1>(GetParam());
+    auto b = std::get<2>(GetParam());
+    EXPECT_TRUE(CheckedMod(a, b) == expect) << std::hex << "0x" << a << " - 0x" << b;
+}
+INSTANTIATE_TEST_SUITE_P(
+    CheckedModTest_AInt,
+    CheckedModTest_AInt,
+    testing::ValuesIn(std::vector<BinaryCheckedCase_AInt>{
+        {AInt(0), AInt(0), AInt(1)},
+        {AInt(0), AInt(1), AInt(1)},
+        {AInt(1), AInt(10), AInt(3)},
+        {AInt(2), AInt(10), AInt(4)},
+        {AInt(0), AInt::Highest(), AInt::Highest()},
+        {AInt(0), AInt::Lowest(), AInt::Lowest()},
+        {OVERFLOW, AInt::Highest(), AInt(0)},
+        {OVERFLOW, AInt::Lowest(), AInt(0)},
+        ////////////////////////////////////////////////////////////////////////
+    }));
+
+using CheckedModTest_Float = testing::TestWithParam<BinaryCheckedCase_Float>;
+TEST_P(CheckedModTest_Float, Test) {
+    auto& p = GetParam();
+    std::visit(
+        [&](auto&& lhs) {
+            using T = std::decay_t<decltype(lhs)>;
+            auto rhs = std::get<T>(std::get<2>(p));
+            auto expect = std::get<std::optional<T>>(std::get<0>(p));
+            EXPECT_TRUE(CheckedMod(lhs, rhs) == expect)
+                << std::hex << "0x" << lhs << " / 0x" << rhs;
+        },
+        std::get<1>(p));
+}
+template <typename T>
+std::vector<BinaryCheckedCase_Float> CheckedModTest_FloatCases() {
+    return {
+        {T(0.5), T(10.5), T(1)},          {T(0.5), T(10.5), T(2)},
+        {T(1.5), T(10.5), T(3)},          {T(2.5), T(10.5), T(4)},
+        {T(0.5), T(10.5), T(5)},          {T(0), T::Highest(), T::Highest()},
+        {T(0), T::Lowest(), T::Lowest()}, {Overflow<T>, T(123), T(0)},
+        {Overflow<T>, T(123), T(-0)},     {Overflow<T>, T(-123), T(0)},
+        {Overflow<T>, T(-123), T(-0)},
+    };
+}
+INSTANTIATE_TEST_SUITE_P(CheckedModTest_Float,
+                         CheckedModTest_Float,
+                         testing::ValuesIn(Concat(CheckedModTest_FloatCases<AFloat>(),
+                                                  CheckedModTest_FloatCases<f32>(),
+                                                  CheckedModTest_FloatCases<f16>())));
+
 using TernaryCheckedCase = std::tuple<std::optional<AInt>, AInt, AInt, AInt>;
 
 using CheckedMaddTest_AInt = testing::TestWithParam<TernaryCheckedCase>;
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index d6d1f00..4677009 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -91,6 +91,7 @@
 #include "src/tint/program.h"
 #include "src/tint/program_id.h"
 #include "src/tint/sem/array.h"
+#include "src/tint/sem/array_count.h"
 #include "src/tint/sem/bool.h"
 #include "src/tint/sem/constant.h"
 #include "src/tint/sem/depth_texture.h"
@@ -457,7 +458,8 @@
     /// @returns the node pointer
     template <typename T, typename... ARGS>
     traits::EnableIf<traits::IsTypeOrDerived<T, sem::Node> &&
-                         !traits::IsTypeOrDerived<T, sem::Type>,
+                         !traits::IsTypeOrDerived<T, sem::Type> &&
+                         !traits::IsTypeOrDerived<T, sem::ArrayCount>,
                      T>*
     create(ARGS&&... args) {
         AssertNotMoved();
@@ -476,17 +478,28 @@
 
     /// Creates a new sem::Type owned by the ProgramBuilder.
     /// When the ProgramBuilder is destructed, owned ProgramBuilder and the
-    /// returned`Type` will also be destructed.
+    /// returned `Type` will also be destructed.
     /// Types are unique (de-aliased), and so calling create() for the same `T`
     /// and arguments will return the same pointer.
     /// @param args the arguments to pass to the type constructor
     /// @returns the de-aliased type pointer
     template <typename T, typename... ARGS>
     traits::EnableIfIsType<T, sem::Type>* create(ARGS&&... args) {
-        static_assert(std::is_base_of<sem::Type, T>::value, "T does not derive from sem::Type");
         AssertNotMoved();
         return types_.Get<T>(std::forward<ARGS>(args)...);
     }
+    /// Creates a new sem::ArrayCount owned by the ProgramBuilder.
+    /// When the ProgramBuilder is destructed, owned ProgramBuilder and the
+    /// returned `ArrayCount` will also be destructed.
+    /// ArrayCounts are unique (de-aliased), and so calling create() for the same `T`
+    /// and arguments will return the same pointer.
+    /// @param args the arguments to pass to the array count constructor
+    /// @returns the de-aliased array count pointer
+    template <typename T, typename... ARGS>
+    traits::EnableIfIsType<T, sem::ArrayCount>* create(ARGS&&... args) {
+        AssertNotMoved();
+        return types_.GetArrayCount<T>(std::forward<ARGS>(args)...);
+    }
 
     /// Marks this builder as moved, preventing any further use of the builder.
     void MarkAsMoved();
diff --git a/src/tint/reader/spirv/function_memory_test.cc b/src/tint/reader/spirv/function_memory_test.cc
index 66979ae..0ff9ba5 100644
--- a/src/tint/reader/spirv/function_memory_test.cc
+++ b/src/tint/reader/spirv/function_memory_test.cc
@@ -953,7 +953,9 @@
     EXPECT_THAT(module_str, HasSubstr(R"(type RTArr = @stride(4) array<u32>;
 
 struct S {
+  /* @offset(0) */
   field0 : u32,
+  /* @offset(4) */
   field1 : RTArr,
 }
 
diff --git a/src/tint/reader/spirv/parser_impl_module_var_test.cc b/src/tint/reader/spirv/parser_impl_module_var_test.cc
index be931a6..7543792 100644
--- a/src/tint/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/tint/reader/spirv/parser_impl_module_var_test.cc
@@ -1254,8 +1254,11 @@
     EXPECT_THAT(module_str, HasSubstr(R"(type Arr = @stride(4) array<u32, 2u>;
 
 struct S {
+  /* @offset(0) */
   field0 : u32,
+  /* @offset(4) */
   field1 : f32,
+  /* @offset(8) */
   field2 : Arr,
 }
 
@@ -1286,6 +1289,7 @@
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
     EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  /* @offset(0) */
   field0 : mat3x2<f32>,
 }
 
@@ -1316,6 +1320,7 @@
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
     EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  /* @offset(0) */
   field0 : mat3x2<f32>,
 }
 
@@ -1346,6 +1351,7 @@
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
     EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  /* @offset(0) */
   @stride(64) @internal(disable_validation__ignore_stride)
   field0 : mat3x2<f32>,
 }
@@ -1399,7 +1405,9 @@
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
     EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  /* @offset(0) */
   field0 : f32,
+  /* @offset(4) */
   field1 : f32,
 }
 
@@ -1428,7 +1436,9 @@
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
     EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  /* @offset(0) */
   field0 : f32,
+  /* @offset(4) */
   field1 : f32,
 }
 
@@ -1459,7 +1469,9 @@
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
     EXPECT_THAT(module_str, HasSubstr(R"(struct S {
+  /* @offset(0) */
   field0 : f32,
+  /* @offset(4) */
   field1 : f32,
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 49e266f..a04e548 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -43,6 +43,7 @@
 #include "src/tint/sem/external_texture.h"
 #include "src/tint/sem/multisampled_texture.h"
 #include "src/tint/sem/sampled_texture.h"
+#include "src/tint/utils/reverse.h"
 #include "src/tint/utils/string.h"
 
 namespace tint::reader::wgsl {
@@ -3239,34 +3240,50 @@
         return component_or_swizzle_specifier(core_expr.value);
     }
 
-    auto check_lhs = [&](ast::UnaryOp op) -> Maybe<const ast::Expression*> {
-        auto& t = peek();
-        auto expr = lhs_expression();
-        if (expr.errored) {
-            return Failure::kErrored;
-        }
-        if (!expr.matched) {
-            return add_error(t, "missing expression");
-        }
-        return create<ast::UnaryOpExpression>(t.source(), op, expr.value);
+    // Gather up all the `*`, `&` and `&&` tokens into a list and create all of the unary ops at
+    // once instead of recursing. This handles the case where the fuzzer decides >8k `*`s would be
+    // fun.
+    struct LHSData {
+        Source source;
+        ast::UnaryOp op;
     };
+    utils::Vector<LHSData, 4> ops;
+    while (true) {
+        auto& t = peek();
+        if (!t.Is(Token::Type::kAndAnd) && !t.Is(Token::Type::kAnd) && !t.Is(Token::Type::kStar)) {
+            break;
+        }
+        next();  // consume the peek
 
-    // If an `&&` is encountered, split it into two `&`'s
-    if (match(Token::Type::kAndAnd)) {
-        // The first `&` is consumed as part of the `&&`, so this needs to run the check itself.
-        split_token(Token::Type::kAnd, Token::Type::kAnd);
-        return check_lhs(ast::UnaryOp::kAddressOf);
+        if (t.Is(Token::Type::kAndAnd)) {
+            // The first `&` is consumed as part of the `&&`, so we only push one of the two `&`s.
+            split_token(Token::Type::kAnd, Token::Type::kAnd);
+            ops.Push({t.source(), ast::UnaryOp::kAddressOf});
+        } else if (t.Is(Token::Type::kAnd)) {
+            ops.Push({t.source(), ast::UnaryOp::kAddressOf});
+        } else if (t.Is(Token::Type::kStar)) {
+            ops.Push({t.source(), ast::UnaryOp::kIndirection});
+        }
+    }
+    if (ops.IsEmpty()) {
+        return Failure::kNoMatch;
     }
 
-    if (match(Token::Type::kAnd)) {
-        return check_lhs(ast::UnaryOp::kAddressOf);
+    auto& t = peek();
+    auto expr = lhs_expression();
+    if (expr.errored) {
+        return Failure::kErrored;
+    }
+    if (!expr.matched) {
+        return add_error(t, "missing expression");
     }
 
-    if (match(Token::Type::kStar)) {
-        return check_lhs(ast::UnaryOp::kIndirection);
+    const ast::Expression* ret = expr.value;
+    // Consume the ops in reverse order so we have the correct AST ordering.
+    for (auto& info : utils::Reverse(ops)) {
+        ret = create<ast::UnaryOpExpression>(info.source, info.op, ret);
     }
-
-    return Failure::kNoMatch;
+    return ret;
 }
 
 // variable_updating_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 aad8eb6..392f0dd 100644
--- a/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_enable_directive_test.cc
@@ -62,7 +62,7 @@
     // Error when unknown extension found
     EXPECT_TRUE(p->has_error());
     EXPECT_EQ(p->error(), R"(1:8: expected extension
-Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_push_constant', 'f16')");
+Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_full_ptr_parameters', 'chromium_experimental_push_constant', 'f16')");
     auto program = p->program();
     auto& ast = program.AST();
     EXPECT_EQ(ast.Enables().Length(), 0u);
@@ -75,7 +75,7 @@
     // Error when unknown extension found
     EXPECT_TRUE(p->has_error());
     EXPECT_EQ(p->error(), R"(1:8: expected extension. Did you mean 'f16'?
-Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_push_constant', 'f16')");
+Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_full_ptr_parameters', 'chromium_experimental_push_constant', 'f16')");
     auto program = p->program();
     auto& ast = program.AST();
     EXPECT_EQ(ast.Enables().Length(), 0u);
@@ -123,7 +123,7 @@
         p->translation_unit();
         EXPECT_TRUE(p->has_error());
         EXPECT_EQ(p->error(), R"(1:8: expected extension
-Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_push_constant', 'f16')");
+Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_full_ptr_parameters', 'chromium_experimental_push_constant', 'f16')");
         auto program = p->program();
         auto& ast = program.AST();
         EXPECT_EQ(ast.Enables().Length(), 0u);
@@ -134,7 +134,7 @@
         p->translation_unit();
         EXPECT_TRUE(p->has_error());
         EXPECT_EQ(p->error(), R"(1:8: expected extension
-Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_push_constant', 'f16')");
+Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_full_ptr_parameters', 'chromium_experimental_push_constant', 'f16')");
         auto program = p->program();
         auto& ast = program.AST();
         EXPECT_EQ(ast.Enables().Length(), 0u);
@@ -145,7 +145,7 @@
         p->translation_unit();
         EXPECT_TRUE(p->has_error());
         EXPECT_EQ(p->error(), R"(1:8: expected extension
-Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_push_constant', 'f16')");
+Possible values: 'chromium_disable_uniformity_analysis', 'chromium_experimental_dp4a', 'chromium_experimental_full_ptr_parameters', 'chromium_experimental_push_constant', 'f16')");
         auto program = p->program();
         auto& ast = program.AST();
         EXPECT_EQ(ast.Enables().Length(), 0u);
diff --git a/src/tint/reader/wgsl/parser_impl_lhs_expression_test.cc b/src/tint/reader/wgsl/parser_impl_lhs_expression_test.cc
index 4e1e0db..a8a476b 100644
--- a/src/tint/reader/wgsl/parser_impl_lhs_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_lhs_expression_test.cc
@@ -75,17 +75,20 @@
 }
 
 TEST_F(ParserImplTest, LHSExpression_Multiple) {
-    auto p = parser("*&**&&*a");
+    auto p = parser("*&********&&&&&&*a");
     auto e = p->lhs_expression();
     ASSERT_FALSE(p->has_error()) << p->error();
     ASSERT_FALSE(e.errored);
     EXPECT_TRUE(e.matched);
     ASSERT_NE(e.value, nullptr);
 
-    std::vector<ast::UnaryOp> results = {ast::UnaryOp::kIndirection, ast::UnaryOp::kAddressOf,
-                                         ast::UnaryOp::kIndirection, ast::UnaryOp::kIndirection,
-                                         ast::UnaryOp::kAddressOf,   ast::UnaryOp::kAddressOf,
-                                         ast::UnaryOp::kIndirection};
+    std::vector<ast::UnaryOp> results = {
+        ast::UnaryOp::kIndirection, ast::UnaryOp::kAddressOf,   ast::UnaryOp::kIndirection,
+        ast::UnaryOp::kIndirection, ast::UnaryOp::kIndirection, ast::UnaryOp::kIndirection,
+        ast::UnaryOp::kIndirection, ast::UnaryOp::kIndirection, ast::UnaryOp::kIndirection,
+        ast::UnaryOp::kIndirection, ast::UnaryOp::kAddressOf,   ast::UnaryOp::kAddressOf,
+        ast::UnaryOp::kAddressOf,   ast::UnaryOp::kAddressOf,   ast::UnaryOp::kAddressOf,
+        ast::UnaryOp::kAddressOf,   ast::UnaryOp::kIndirection};
 
     auto* expr = e.value;
     for (auto op : results) {
diff --git a/src/tint/resolver/call_validation_test.cc b/src/tint/resolver/call_validation_test.cc
index 82037af..e94e7b8 100644
--- a/src/tint/resolver/call_validation_test.cc
+++ b/src/tint/resolver/call_validation_test.cc
@@ -114,11 +114,29 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverCallValidationTest, PointerArgument_NotWholeVar) {
+TEST_F(ResolverCallValidationTest, PointerArgument_LetIdentExpr) {
+    // fn foo(p: ptr<function, i32>) {}
+    // fn main() {
+    //   let z: i32 = 1i;
+    //   foo(&z);
+    // }
+    auto* param = Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction));
+    Func("foo", utils::Vector{param}, ty.void_(), utils::Empty);
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("z", ty.i32(), Expr(1_i))),
+             CallStmt(Call("foo", AddressOf(Expr(Source{{12, 34}}, "z")))),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: cannot take the address of expression");
+}
+
+TEST_F(ResolverCallValidationTest, PointerArgument_AddressOfFunctionMember) {
     // struct S { m: i32; };
     // fn foo(p: ptr<function, i32>) {}
     // fn main() {
-    //   var v: S;
+    //   var v : S;
     //   foo(&v.m);
     // }
     auto* S = Structure("S", utils::Vector{
@@ -138,6 +156,51 @@
               "originating variable");
 }
 
+TEST_F(ResolverCallValidationTest,
+       PointerArgument_AddressOfFunctionMember_WithFullPtrParametersExt) {
+    // enable chromium_experimental_full_ptr_parameters;
+    // struct S { m: i32; };
+    // fn foo(p: ptr<function, i32>) {}
+    // fn main() {
+    //   var v : S;
+    //   foo(&v.m);
+    // }
+    Enable(ast::Extension::kChromiumExperimentalFullPtrParameters);
+    auto* S = Structure("S", utils::Vector{
+                                 Member("m", ty.i32()),
+                             });
+    auto* param = Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction));
+    Func("foo", utils::Vector{param}, ty.void_(), utils::Empty);
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Var("v", ty.Of(S))),
+             CallStmt(Call("foo", AddressOf(Source{{12, 34}}, MemberAccessor("v", "m")))),
+         });
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+TEST_F(ResolverCallValidationTest, PointerArgument_AddressOfLetMember) {
+    // struct S { m: i32; };
+    // fn foo(p: ptr<function, i32>) {}
+    // fn main() {
+    //   let v: S = S();
+    //   foo(&v.m);
+    // }
+    auto* S = Structure("S", utils::Vector{
+                                 Member("m", ty.i32()),
+                             });
+    auto* param = Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction));
+    Func("foo", utils::Vector{param}, ty.void_(), utils::Empty);
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Let("v", ty.Of(S), Construct(ty.Of(S)))),
+             CallStmt(Call("foo", AddressOf(MemberAccessor(Source{{12, 34}}, "v", "m")))),
+         });
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: cannot take the address of expression");
+}
+
 TEST_F(ResolverCallValidationTest, PointerArgument_FunctionParam) {
     // fn foo(p: ptr<function, i32>) {}
     // fn bar(p: ptr<function, i32>) {
@@ -181,12 +244,12 @@
          },
          ty.void_(),
          utils::Vector{
-             CallStmt(Call("foo", Expr("p"))),
+             CallStmt(Call("foo", "p")),
          });
     Func("main", utils::Empty, ty.void_(),
          utils::Vector{
              Decl(Var("v", ty.i32(), Expr(1_i))),
-             CallStmt(Call("foo", AddressOf(Expr("v")))),
+             CallStmt(Call("foo", AddressOf("v"))),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -195,40 +258,15 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverCallValidationTest, PointerArgument_FunctionParam_NotWholeVar) {
-    // fn foo(p: ptr<function, i32>) {}
-    // fn bar(p: ptr<function, array<i32, 4>>) {
-    //   foo(&(*p)[0]);
-    // }
-    Func("foo",
-         utils::Vector{
-             Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction)),
-         },
-         ty.void_(), utils::Empty);
-    Func("bar",
-         utils::Vector{
-             Param("p", ty.pointer(ty.array<i32, 4>(), ast::AddressSpace::kFunction)),
-         },
-         ty.void_(),
-         utils::Vector{
-             CallStmt(Call("foo", AddressOf(Source{{12, 34}}, IndexAccessor(Deref("p"), 0_a)))),
-         });
-
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: arguments of pointer type must not point to a subset of the "
-              "originating variable");
-}
-
 TEST_F(ResolverCallValidationTest, LetPointer) {
     // fn foo(p : ptr<function, i32>) {}
     // @fragment
     // fn main() {
     //   var v: i32;
     //   let p: ptr<function, i32> = &v;
-    //   foo(p);
+    //   x(p);
     // }
-    Func("foo",
+    Func("x",
          utils::Vector{
              Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction)),
          },
@@ -237,7 +275,7 @@
          utils::Vector{
              Decl(Var("v", ty.i32())),
              Decl(Let("p", ty.pointer(ty.i32(), ast::AddressSpace::kFunction), AddressOf("v"))),
-             CallStmt(Call("foo", Expr(Source{{12, 34}}, "p"))),
+             CallStmt(Call("x", "p")),
          },
          utils::Vector{
              Stage(ast::PipelineStage::kFragment),
@@ -247,10 +285,10 @@
 
 TEST_F(ResolverCallValidationTest, LetPointerPrivate) {
     // fn foo(p : ptr<private, i32>) {}
-    // var<private> v: i32;
+    // var v : i32;
     // @fragment
     // fn main() {
-    //   let p: ptr<private, i32> = &v;
+    //   let p : ptr<private, i32> = &v;
     //   foo(p);
     // }
     Func("foo",
@@ -299,6 +337,34 @@
               "originating variable");
 }
 
+TEST_F(ResolverCallValidationTest, LetPointer_NotWholeVar_WithFullPtrParametersExt) {
+    // enable chromium_experimental_full_ptr_parameters;
+    // fn foo(p : ptr<function, i32>) {}
+    // @fragment
+    // fn main() {
+    //   var v: array<i32, 4>;
+    //   let p: ptr<function, i32> = &(v[0]);
+    //   x(p);
+    // }
+    Enable(ast::Extension::kChromiumExperimentalFullPtrParameters);
+    Func("foo",
+         utils::Vector{
+             Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction)),
+         },
+         ty.void_(), utils::Empty);
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Var("v", ty.array<i32, 4>())),
+             Decl(Let("p", ty.pointer(ty.i32(), ast::AddressSpace::kFunction),
+                      AddressOf(IndexAccessor("v", 0_a)))),
+             CallStmt(Call("foo", Expr(Source{{12, 34}}, "p"))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
+    EXPECT_TRUE(r()->Resolve());
+}
+
 TEST_F(ResolverCallValidationTest, ComplexPointerChain) {
     // fn foo(p : ptr<function, array<i32, 4>>) {}
     // @fragment
@@ -360,6 +426,37 @@
               "originating variable");
 }
 
+TEST_F(ResolverCallValidationTest, ComplexPointerChain_NotWholeVar_WithFullPtrParametersExt) {
+    // enable chromium_experimental_full_ptr_parameters;
+    // fn foo(p : ptr<function, i32>) {}
+    // @fragment
+    // fn main() {
+    //   var v: array<i32, 4>;
+    //   let p1 = &v;
+    //   let p2 = p1;
+    //   let p3 = &(*p2)[0];
+    //   foo(&*p);
+    // }
+    Enable(ast::Extension::kChromiumExperimentalFullPtrParameters);
+    Func("foo",
+         utils::Vector{
+             Param("p", ty.pointer<i32>(ast::AddressSpace::kFunction)),
+         },
+         ty.void_(), utils::Empty);
+    Func("main", utils::Empty, ty.void_(),
+         utils::Vector{
+             Decl(Var("v", ty.array<i32, 4>())),
+             Decl(Let("p1", AddressOf("v"))),
+             Decl(Let("p2", Expr("p1"))),
+             Decl(Let("p3", AddressOf(IndexAccessor(Deref("p2"), 0_a)))),
+             CallStmt(Call("foo", AddressOf(Source{{12, 34}}, Deref("p3")))),
+         },
+         utils::Vector{
+             Stage(ast::PipelineStage::kFragment),
+         });
+    EXPECT_TRUE(r()->Resolve());
+}
+
 TEST_F(ResolverCallValidationTest, CallVariable) {
     // var v : i32;
     // fn f() {
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index 7195824..8170adb 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -744,7 +744,6 @@
     using T = UnwrapNumber<NumberT>;
     NumberT result;
     if constexpr (IsAbstract<NumberT> || IsFloatingPoint<NumberT>) {
-        // Check for over/underflow for abstract values
         if (auto r = CheckedMul(a, b)) {
             result = r->value;
         } else {
@@ -770,7 +769,6 @@
 utils::Result<NumberT> ConstEval::Div(const Source& source, NumberT a, NumberT b) {
     NumberT result;
     if constexpr (IsAbstract<NumberT> || IsFloatingPoint<NumberT>) {
-        // Check for over/underflow for abstract values
         if (auto r = CheckedDiv(a, b)) {
             result = r->value;
         } else {
@@ -800,6 +798,38 @@
 }
 
 template <typename NumberT>
+utils::Result<NumberT> ConstEval::Mod(const Source& source, NumberT a, NumberT b) {
+    NumberT result;
+    if constexpr (IsAbstract<NumberT> || IsFloatingPoint<NumberT>) {
+        if (auto r = CheckedMod(a, b)) {
+            result = r->value;
+        } else {
+            AddError(OverflowErrorMessage(a, "%", b), source);
+            return utils::Failure;
+        }
+    } else {
+        using T = UnwrapNumber<NumberT>;
+        auto lhs = a.value;
+        auto rhs = b.value;
+        if (rhs == 0) {
+            // lhs % 0 is an error
+            AddError(OverflowErrorMessage(a, "%", b), source);
+            return utils::Failure;
+        }
+        if constexpr (std::is_signed_v<T>) {
+            // For signed integers, lhs % -1 where lhs is the
+            // most negative value is an error
+            if (rhs == -1 && lhs == std::numeric_limits<T>::min()) {
+                AddError(OverflowErrorMessage(a, "%", b), source);
+                return utils::Failure;
+            }
+        }
+        result = lhs % rhs;
+    }
+    return result;
+}
+
+template <typename NumberT>
 utils::Result<NumberT> ConstEval::Dot2(const Source& source,
                                        NumberT a1,
                                        NumberT a2,
@@ -1111,6 +1141,15 @@
     };
 }
 
+auto ConstEval::ModFunc(const Source& source, const sem::Type* elem_ty) {
+    return [=](auto a1, auto a2) -> ImplResult {
+        if (auto r = Mod(source, a1, a2)) {
+            return CreateElement(builder, source, elem_ty, r.Get());
+        }
+        return utils::Failure;
+    };
+}
+
 auto ConstEval::Dot2Func(const Source& source, const sem::Type* elem_ty) {
     return [=](auto a1, auto a2, auto b1, auto b2) -> ImplResult {
         if (auto r = Dot2(source, a1, a2, b1, b2)) {
@@ -1683,6 +1722,16 @@
     return TransformBinaryElements(builder, ty, transform, args[0], args[1]);
 }
 
+ConstEval::Result ConstEval::OpModulo(const sem::Type* ty,
+                                      utils::VectorRef<const sem::Constant*> args,
+                                      const Source& source) {
+    auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
+        return Dispatch_fia_fiu32_f16(ModFunc(source, c0->Type()), c0, c1);
+    };
+
+    return TransformBinaryElements(builder, ty, transform, args[0], args[1]);
+}
+
 ConstEval::Result ConstEval::OpEqual(const sem::Type* ty,
                                      utils::VectorRef<const sem::Constant*> args,
                                      const Source& source) {
diff --git a/src/tint/resolver/const_eval.h b/src/tint/resolver/const_eval.h
index d92dc3f..9905036 100644
--- a/src/tint/resolver/const_eval.h
+++ b/src/tint/resolver/const_eval.h
@@ -283,6 +283,15 @@
                     utils::VectorRef<const sem::Constant*> args,
                     const Source& source);
 
+    /// Modulo operator '%'
+    /// @param ty the expression type
+    /// @param args the input arguments
+    /// @param source the source location
+    /// @return the result value, or null if the value cannot be calculated
+    Result OpModulo(const sem::Type* ty,
+                    utils::VectorRef<const sem::Constant*> args,
+                    const Source& source);
+
     /// Equality operator '=='
     /// @param ty the expression type
     /// @param args the input arguments
@@ -1040,6 +1049,14 @@
     template <typename NumberT>
     utils::Result<NumberT> Div(const Source& source, NumberT a, NumberT b);
 
+    /// Returns the (signed) remainder of the division of two Number<T>s
+    /// @param source the source location
+    /// @param a the lhs number
+    /// @param b the rhs number
+    /// @returns the result number on success, or logs an error and returns Failure
+    template <typename NumberT>
+    utils::Result<NumberT> Mod(const Source& source, NumberT a, NumberT b);
+
     /// Returns the dot product of (a1,a2) with (b1,b2)
     /// @param source the source location
     /// @param a1 component 1 of lhs vector
@@ -1216,6 +1233,13 @@
     /// @returns the callable function
     auto DivFunc(const Source& source, const sem::Type* elem_ty);
 
+    /// Returns a callable that calls Mod, and creates a Constant with its result of type `elem_ty`
+    /// if successful, or returns Failure otherwise.
+    /// @param source the source location
+    /// @param elem_ty the element type of the Constant to create on success
+    /// @returns the callable function
+    auto ModFunc(const Source& source, const sem::Type* elem_ty);
+
     /// Returns a callable that calls Dot2, and creates a Constant with its result of type `elem_ty`
     /// if successful, or returns Failure otherwise.
     /// @param source the source location
diff --git a/src/tint/resolver/const_eval_binary_op_test.cc b/src/tint/resolver/const_eval_binary_op_test.cc
index 2a13360..2590466 100644
--- a/src/tint/resolver/const_eval_binary_op_test.cc
+++ b/src/tint/resolver/const_eval_binary_op_test.cc
@@ -485,6 +485,161 @@
                                  OpDivFloatCases<f32>(),
                                  OpDivFloatCases<f16>()))));
 
+template <typename T>
+std::vector<Case> OpModCases() {
+    auto error_msg = [](auto a, auto b) {
+        return "12:34 error: " + OverflowErrorMessage(a, "%", b);
+    };
+
+    // Common cases for all types
+    std::vector<Case> r = {
+        C(T{0}, T{1}, T{0}),    //
+        C(T{1}, T{1}, T{0}),    //
+        C(T{10}, T{1}, T{0}),   //
+        C(T{10}, T{2}, T{0}),   //
+        C(T{10}, T{3}, T{1}),   //
+        C(T{10}, T{4}, T{2}),   //
+        C(T{10}, T{5}, T{0}),   //
+        C(T{10}, T{6}, T{4}),   //
+        C(T{10}, T{5}, T{0}),   //
+        C(T{10}, T{8}, T{2}),   //
+        C(T{10}, T{9}, T{1}),   //
+        C(T{10}, T{10}, T{0}),  //
+
+        // Error on divide by zero
+        E(T{123}, T{0}, error_msg(T{123}, T{0})),
+        E(T::Highest(), T{0}, error_msg(T::Highest(), T{0})),
+        E(T::Lowest(), T{0}, error_msg(T::Lowest(), T{0})),
+    };
+
+    if constexpr (IsIntegral<T>) {
+        ConcatInto(  //
+            r, std::vector<Case>{
+                   C(T::Highest(), T{T::Highest() - T{1}}, T{1}),
+               });
+    }
+
+    if constexpr (IsSignedIntegral<T>) {
+        ConcatInto(  //
+            r, std::vector<Case>{
+                   C(T::Lowest(), T{T::Lowest() + T{1}}, -T(1)),
+
+                   // Error on most negative integer divided by -1
+                   E(T::Lowest(), T{-1}, error_msg(T::Lowest(), T{-1})),
+               });
+    }
+
+    // Negative values (both signed integrals and floating point)
+    if constexpr (IsSignedIntegral<T> || IsFloatingPoint<T>) {
+        ConcatInto(  //
+            r, std::vector<Case>{
+                   C(-T{1}, T{1}, T{0}),  //
+
+                   // lhs negative, rhs positive
+                   C(-T{10}, T{1}, T{0}),   //
+                   C(-T{10}, T{2}, T{0}),   //
+                   C(-T{10}, T{3}, -T{1}),  //
+                   C(-T{10}, T{4}, -T{2}),  //
+                   C(-T{10}, T{5}, T{0}),   //
+                   C(-T{10}, T{6}, -T{4}),  //
+                   C(-T{10}, T{5}, T{0}),   //
+                   C(-T{10}, T{8}, -T{2}),  //
+                   C(-T{10}, T{9}, -T{1}),  //
+                   C(-T{10}, T{10}, T{0}),  //
+
+                   // lhs positive, rhs negative
+                   C(T{10}, -T{1}, T{0}),   //
+                   C(T{10}, -T{2}, T{0}),   //
+                   C(T{10}, -T{3}, T{1}),   //
+                   C(T{10}, -T{4}, T{2}),   //
+                   C(T{10}, -T{5}, T{0}),   //
+                   C(T{10}, -T{6}, T{4}),   //
+                   C(T{10}, -T{5}, T{0}),   //
+                   C(T{10}, -T{8}, T{2}),   //
+                   C(T{10}, -T{9}, T{1}),   //
+                   C(T{10}, -T{10}, T{0}),  //
+
+                   // lhs negative, rhs negative
+                   C(-T{10}, -T{1}, T{0}),   //
+                   C(-T{10}, -T{2}, T{0}),   //
+                   C(-T{10}, -T{3}, -T{1}),  //
+                   C(-T{10}, -T{4}, -T{2}),  //
+                   C(-T{10}, -T{5}, T{0}),   //
+                   C(-T{10}, -T{6}, -T{4}),  //
+                   C(-T{10}, -T{5}, T{0}),   //
+                   C(-T{10}, -T{8}, -T{2}),  //
+                   C(-T{10}, -T{9}, -T{1}),  //
+                   C(-T{10}, -T{10}, T{0}),  //
+               });
+    }
+
+    // Float values
+    if constexpr (IsFloatingPoint<T>) {
+        ConcatInto(  //
+            r, std::vector<Case>{
+                   C(T{10.5}, T{1}, T{0.5}),   //
+                   C(T{10.5}, T{2}, T{0.5}),   //
+                   C(T{10.5}, T{3}, T{1.5}),   //
+                   C(T{10.5}, T{4}, T{2.5}),   //
+                   C(T{10.5}, T{5}, T{0.5}),   //
+                   C(T{10.5}, T{6}, T{4.5}),   //
+                   C(T{10.5}, T{5}, T{0.5}),   //
+                   C(T{10.5}, T{8}, T{2.5}),   //
+                   C(T{10.5}, T{9}, T{1.5}),   //
+                   C(T{10.5}, T{10}, T{0.5}),  //
+
+                   // lhs negative, rhs positive
+                   C(-T{10.5}, T{1}, -T{0.5}),   //
+                   C(-T{10.5}, T{2}, -T{0.5}),   //
+                   C(-T{10.5}, T{3}, -T{1.5}),   //
+                   C(-T{10.5}, T{4}, -T{2.5}),   //
+                   C(-T{10.5}, T{5}, -T{0.5}),   //
+                   C(-T{10.5}, T{6}, -T{4.5}),   //
+                   C(-T{10.5}, T{5}, -T{0.5}),   //
+                   C(-T{10.5}, T{8}, -T{2.5}),   //
+                   C(-T{10.5}, T{9}, -T{1.5}),   //
+                   C(-T{10.5}, T{10}, -T{0.5}),  //
+
+                   // lhs positive, rhs negative
+                   C(T{10.5}, -T{1}, T{0.5}),   //
+                   C(T{10.5}, -T{2}, T{0.5}),   //
+                   C(T{10.5}, -T{3}, T{1.5}),   //
+                   C(T{10.5}, -T{4}, T{2.5}),   //
+                   C(T{10.5}, -T{5}, T{0.5}),   //
+                   C(T{10.5}, -T{6}, T{4.5}),   //
+                   C(T{10.5}, -T{5}, T{0.5}),   //
+                   C(T{10.5}, -T{8}, T{2.5}),   //
+                   C(T{10.5}, -T{9}, T{1.5}),   //
+                   C(T{10.5}, -T{10}, T{0.5}),  //
+
+                   // lhs negative, rhs negative
+                   C(-T{10.5}, -T{1}, -T{0.5}),   //
+                   C(-T{10.5}, -T{2}, -T{0.5}),   //
+                   C(-T{10.5}, -T{3}, -T{1.5}),   //
+                   C(-T{10.5}, -T{4}, -T{2.5}),   //
+                   C(-T{10.5}, -T{5}, -T{0.5}),   //
+                   C(-T{10.5}, -T{6}, -T{4.5}),   //
+                   C(-T{10.5}, -T{5}, -T{0.5}),   //
+                   C(-T{10.5}, -T{8}, -T{2.5}),   //
+                   C(-T{10.5}, -T{9}, -T{1.5}),   //
+                   C(-T{10.5}, -T{10}, -T{0.5}),  //
+               });
+    }
+
+    return r;
+}
+INSTANTIATE_TEST_SUITE_P(Mod,
+                         ResolverConstEvalBinaryOpTest,
+                         testing::Combine(  //
+                             testing::Values(ast::BinaryOp::kModulo),
+                             testing::ValuesIn(Concat(  //
+                                 OpModCases<AInt>(),
+                                 OpModCases<i32>(),
+                                 OpModCases<u32>(),
+                                 OpModCases<AFloat>(),
+                                 OpModCases<f32>(),
+                                 OpModCases<f16>()))));
+
 template <typename T, bool equals>
 std::vector<Case> OpEqualCases() {
     return {
diff --git a/src/tint/resolver/const_eval_construction_test.cc b/src/tint/resolver/const_eval_construction_test.cc
index 9df9807..cabc4ab 100644
--- a/src/tint/resolver/const_eval_construction_test.cc
+++ b/src/tint/resolver/const_eval_construction_test.cc
@@ -1321,7 +1321,6 @@
     auto* arr = sem->Type()->As<sem::Array>();
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<sem::I32>());
-    EXPECT_EQ(arr->Count(), sem::ConstantArrayCount{4u});
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
@@ -1359,7 +1358,6 @@
     auto* arr = sem->Type()->As<sem::Array>();
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<sem::F32>());
-    EXPECT_EQ(arr->Count(), sem::ConstantArrayCount{4u});
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
@@ -1397,7 +1395,6 @@
     auto* arr = sem->Type()->As<sem::Array>();
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<sem::Vector>());
-    EXPECT_EQ(arr->Count(), sem::ConstantArrayCount{2u});
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
@@ -1449,7 +1446,6 @@
     auto* arr = sem->Type()->As<sem::Array>();
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<sem::Struct>());
-    EXPECT_EQ(arr->Count(), sem::ConstantArrayCount{2u});
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_TRUE(sem->ConstantValue()->AllEqual());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
@@ -1487,7 +1483,6 @@
     auto* arr = sem->Type()->As<sem::Array>();
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<sem::I32>());
-    EXPECT_EQ(arr->Count(), sem::ConstantArrayCount{4u});
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
@@ -1525,7 +1520,6 @@
     auto* arr = sem->Type()->As<sem::Array>();
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<sem::F32>());
-    EXPECT_EQ(arr->Count(), sem::ConstantArrayCount{4u});
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
@@ -1564,7 +1558,6 @@
     auto* arr = sem->Type()->As<sem::Array>();
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<sem::Vector>());
-    EXPECT_EQ(arr->Count(), sem::ConstantArrayCount{2u});
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
@@ -1594,7 +1587,6 @@
     auto* arr = sem->Type()->As<sem::Array>();
     ASSERT_NE(arr, nullptr);
     EXPECT_TRUE(arr->ElemType()->Is<sem::Struct>());
-    EXPECT_EQ(arr->Count(), sem::ConstantArrayCount{2u});
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_FALSE(sem->ConstantValue()->AllEqual());
     EXPECT_FALSE(sem->ConstantValue()->AnyZero());
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index 796412b..29f88a7 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -66,6 +66,7 @@
 #include "src/tint/ast/void.h"
 #include "src/tint/ast/while_statement.h"
 #include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/resolver/type_alias.h"
 #include "src/tint/scope_stack.h"
 #include "src/tint/sem/builtin.h"
 #include "src/tint/symbol_table.h"
@@ -481,9 +482,14 @@
         graph_.resolved_symbols.Add(from, resolved);
     }
 
-    /// @returns true if `name` is the name of a builtin function
+    /// @returns true if `name` is the name of a builtin function, or builtin type alias
     bool IsBuiltin(Symbol name) const {
-        return sem::ParseBuiltinType(symbols_.NameFor(name)) != sem::BuiltinType::kNone;
+        auto s = symbols_.NameFor(name);
+        if (sem::ParseBuiltinType(s) != sem::BuiltinType::kNone ||
+            ParseTypeAlias(s) != TypeAlias::kUndefined) {
+            return true;
+        }
+        return false;
     }
 
     /// Appends an error to the diagnostics that the given symbol cannot be
diff --git a/src/tint/resolver/function_validation_test.cc b/src/tint/resolver/function_validation_test.cc
index 808f556..922a1dc 100644
--- a/src/tint/resolver/function_validation_test.cc
+++ b/src/tint/resolver/function_validation_test.cc
@@ -1041,21 +1041,26 @@
     EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
 }
 
+enum class Expectation {
+    kAlwaysPass,
+    kPassWithFullPtrParameterExtension,
+    kAlwaysFail,
+};
 struct TestParams {
     ast::AddressSpace address_space;
-    bool should_pass;
+    Expectation expectation;
 };
 
 struct TestWithParams : ResolverTestWithParam<TestParams> {};
 
 using ResolverFunctionParameterValidationTest = TestWithParams;
-TEST_P(ResolverFunctionParameterValidationTest, AddressSpace) {
+TEST_P(ResolverFunctionParameterValidationTest, AddressSpaceNoExtension) {
     auto& param = GetParam();
     auto* ptr_type = ty.pointer(Source{{12, 34}}, ty.i32(), param.address_space);
     auto* arg = Param(Source{{12, 34}}, "p", ptr_type);
     Func("f", utils::Vector{arg}, ty.void_(), utils::Empty);
 
-    if (param.should_pass) {
+    if (param.expectation == Expectation::kAlwaysPass) {
         ASSERT_TRUE(r()->Resolve()) << r()->error();
     } else {
         std::stringstream ss;
@@ -1065,17 +1070,36 @@
                                     ss.str() + "' address space");
     }
 }
-INSTANTIATE_TEST_SUITE_P(ResolverTest,
-                         ResolverFunctionParameterValidationTest,
-                         testing::Values(TestParams{ast::AddressSpace::kNone, false},
-                                         TestParams{ast::AddressSpace::kIn, false},
-                                         TestParams{ast::AddressSpace::kOut, false},
-                                         TestParams{ast::AddressSpace::kUniform, false},
-                                         TestParams{ast::AddressSpace::kWorkgroup, false},
-                                         TestParams{ast::AddressSpace::kHandle, false},
-                                         TestParams{ast::AddressSpace::kStorage, false},
-                                         TestParams{ast::AddressSpace::kPrivate, true},
-                                         TestParams{ast::AddressSpace::kFunction, true}));
+TEST_P(ResolverFunctionParameterValidationTest, AddressSpaceWithExtension) {
+    auto& param = GetParam();
+    auto* ptr_type = ty.pointer(Source{{12, 34}}, ty.i32(), param.address_space);
+    auto* arg = Param(Source{{12, 34}}, "p", ptr_type);
+    Enable(ast::Extension::kChromiumExperimentalFullPtrParameters);
+    Func("f", utils::Vector{arg}, ty.void_(), utils::Empty);
+
+    if (param.expectation != Expectation::kAlwaysFail) {
+        ASSERT_TRUE(r()->Resolve()) << r()->error();
+    } else {
+        std::stringstream ss;
+        ss << param.address_space;
+        EXPECT_FALSE(r()->Resolve());
+        EXPECT_EQ(r()->error(), "12:34 error: function parameter of pointer type cannot be in '" +
+                                    ss.str() + "' address space");
+    }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverTest,
+    ResolverFunctionParameterValidationTest,
+    testing::Values(
+        TestParams{ast::AddressSpace::kNone, Expectation::kAlwaysFail},
+        TestParams{ast::AddressSpace::kIn, Expectation::kAlwaysFail},
+        TestParams{ast::AddressSpace::kOut, Expectation::kAlwaysFail},
+        TestParams{ast::AddressSpace::kUniform, Expectation::kPassWithFullPtrParameterExtension},
+        TestParams{ast::AddressSpace::kWorkgroup, Expectation::kPassWithFullPtrParameterExtension},
+        TestParams{ast::AddressSpace::kHandle, Expectation::kAlwaysFail},
+        TestParams{ast::AddressSpace::kStorage, Expectation::kPassWithFullPtrParameterExtension},
+        TestParams{ast::AddressSpace::kPrivate, Expectation::kAlwaysPass},
+        TestParams{ast::AddressSpace::kFunction, Expectation::kAlwaysPass}));
 
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/inferred_type_test.cc b/src/tint/resolver/inferred_type_test.cc
index d2f649b..ddbc8f8 100644
--- a/src/tint/resolver/inferred_type_test.cc
+++ b/src/tint/resolver/inferred_type_test.cc
@@ -135,8 +135,8 @@
 
 TEST_F(ResolverInferredTypeTest, InferArray_Pass) {
     auto* type = ty.array(ty.u32(), 10_u);
-    auto* expected_type =
-        create<sem::Array>(create<sem::U32>(), sem::ConstantArrayCount{10u}, 4u, 4u * 10u, 4u, 4u);
+    auto* expected_type = create<sem::Array>(
+        create<sem::U32>(), create<sem::ConstantArrayCount>(10u), 4u, 4u * 10u, 4u, 4u);
 
     auto* ctor_expr = Construct(type);
     auto* var = Var("a", ast::AddressSpace::kFunction, ctor_expr);
@@ -150,11 +150,12 @@
     auto* member = Member("x", ty.i32());
     auto* str = Structure("S", utils::Vector{member});
 
-    auto* expected_type = create<sem::Struct>(
-        str, str->name,
-        sem::StructMemberList{create<sem::StructMember>(member, member->symbol, create<sem::I32>(),
-                                                        0u, 0u, 0u, 4u, std::nullopt)},
-        0u, 4u, 4u);
+    auto* expected_type =
+        create<sem::Struct>(str, str->source, str->name,
+                            sem::StructMemberList{create<sem::StructMember>(
+                                member, member->source, member->symbol, create<sem::I32>(), 0u, 0u,
+                                0u, 4u, std::nullopt)},
+                            0u, 4u, 4u);
 
     auto* ctor_expr = Construct(ty.Of(str));
 
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index a04eca4..ebd5043 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -523,7 +523,7 @@
     }
 
     if (auto* a = ty->As<sem::Array>()) {
-        if (a->IsRuntimeSized()) {
+        if (a->Count()->Is<sem::RuntimeArrayCount>()) {
             T = a->ElemType();
             return true;
         }
@@ -532,12 +532,13 @@
 }
 
 const sem::Array* build_array(MatchState& state, const sem::Type* el) {
-    return state.builder.create<sem::Array>(el,
-                                            /* count */ sem::RuntimeArrayCount{},
-                                            /* align */ 0u,
-                                            /* size */ 0u,
-                                            /* stride */ 0u,
-                                            /* stride_implicit */ 0u);
+    return state.builder.create<sem::Array>(
+        el,
+        /* count */ state.builder.create<sem::RuntimeArrayCount>(),
+        /* align */ 0u,
+        /* size */ 0u,
+        /* stride */ 0u,
+        /* stride_implicit */ 0u);
 }
 
 bool match_ptr(MatchState&, const sem::Type* ty, Number& S, const sem::Type*& T, Number& A) {
@@ -809,6 +810,7 @@
         max_align = std::max(max_align, align);
         members.emplace_back(b.create<sem::StructMember>(
             /* declaration */ nullptr,
+            /* source */ Source{},
             /* name */ b.Sym(m.name),
             /* type */ m.type,
             /* index */ static_cast<uint32_t>(members.size()),
@@ -822,6 +824,7 @@
     uint32_t size_with_padding = utils::RoundUp(max_align, offset);
     return b.create<sem::Struct>(
         /* declaration */ nullptr,
+        /* source */ Source{},
         /* name */ b.Sym(name),
         /* members */ members,
         /* align */ max_align,
diff --git a/src/tint/resolver/intrinsic_table.inl b/src/tint/resolver/intrinsic_table.inl
index d0752da..00b7557 100644
--- a/src/tint/resolver/intrinsic_table.inl
+++ b/src/tint/resolver/intrinsic_table.inl
@@ -6900,23 +6900,23 @@
   },
   {
     /* [782] */
-    /* usage */ ParameterUsage::kNone,
+    /* usage */ ParameterUsage::kX,
     /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [783] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[101],
+    /* usage */ ParameterUsage::kY,
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [784] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* usage */ ParameterUsage::kXy,
+    /* matcher indices */ &kMatcherIndices[150],
   },
   {
     /* [785] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[99],
+    /* usage */ ParameterUsage::kZ,
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [786] */
@@ -6925,8 +6925,8 @@
   },
   {
     /* [787] */
-    /* usage */ ParameterUsage::kY,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* usage */ ParameterUsage::kYz,
+    /* matcher indices */ &kMatcherIndices[150],
   },
   {
     /* [788] */
@@ -6935,78 +6935,78 @@
   },
   {
     /* [789] */
-    /* usage */ ParameterUsage::kZ,
-    /* matcher indices */ &kMatcherIndices[3],
-  },
-  {
-    /* [790] */
-    /* usage */ ParameterUsage::kX,
-    /* matcher indices */ &kMatcherIndices[3],
-  },
-  {
-    /* [791] */
-    /* usage */ ParameterUsage::kYz,
-    /* matcher indices */ &kMatcherIndices[150],
-  },
-  {
-    /* [792] */
-    /* usage */ ParameterUsage::kXy,
-    /* matcher indices */ &kMatcherIndices[150],
-  },
-  {
-    /* [793] */
     /* usage */ ParameterUsage::kZw,
     /* matcher indices */ &kMatcherIndices[150],
   },
   {
-    /* [794] */
+    /* [790] */
     /* usage */ ParameterUsage::kXyz,
     /* matcher indices */ &kMatcherIndices[102],
   },
   {
-    /* [795] */
+    /* [791] */
     /* usage */ ParameterUsage::kW,
     /* matcher indices */ &kMatcherIndices[3],
   },
   {
-    /* [796] */
+    /* [792] */
     /* usage */ ParameterUsage::kX,
     /* matcher indices */ &kMatcherIndices[3],
   },
   {
-    /* [797] */
+    /* [793] */
     /* usage */ ParameterUsage::kZyw,
     /* matcher indices */ &kMatcherIndices[102],
   },
   {
+    /* [794] */
+    /* usage */ ParameterUsage::kNone,
+    /* matcher indices */ &kMatcherIndices[150],
+  },
+  {
+    /* [795] */
+    /* usage */ ParameterUsage::kNone,
+    /* matcher indices */ &kMatcherIndices[150],
+  },
+  {
+    /* [796] */
+    /* usage */ ParameterUsage::kNone,
+    /* matcher indices */ &kMatcherIndices[102],
+  },
+  {
+    /* [797] */
+    /* usage */ ParameterUsage::kNone,
+    /* matcher indices */ &kMatcherIndices[102],
+  },
+  {
     /* [798] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[150],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [799] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[150],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [800] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[102],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [801] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[102],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [802] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[130],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [803] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[130],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [804] */
@@ -7021,47 +7021,47 @@
   {
     /* [806] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[35],
   },
   {
     /* [807] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[33],
   },
   {
     /* [808] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[35],
   },
   {
     /* [809] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[33],
   },
   {
     /* [810] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[35],
+    /* matcher indices */ &kMatcherIndices[0],
   },
   {
     /* [811] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[33],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [812] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[35],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [813] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[33],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [814] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[0],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [815] */
@@ -7156,27 +7156,27 @@
   {
     /* [833] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[10],
   },
   {
     /* [834] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[38],
   },
   {
     /* [835] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[36],
   },
   {
     /* [836] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[38],
   },
   {
     /* [837] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[10],
+    /* matcher indices */ &kMatcherIndices[36],
   },
   {
     /* [838] */
@@ -7221,22 +7221,22 @@
   {
     /* [846] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[38],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [847] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[36],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [848] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[38],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [849] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[36],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [850] */
@@ -7291,22 +7291,22 @@
   {
     /* [860] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[38],
   },
   {
     /* [861] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[36],
   },
   {
     /* [862] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[38],
   },
   {
     /* [863] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[36],
   },
   {
     /* [864] */
@@ -7321,22 +7321,22 @@
   {
     /* [866] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[38],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [867] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[36],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [868] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[38],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [869] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[36],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [870] */
@@ -7371,62 +7371,62 @@
   {
     /* [876] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [877] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[108],
   },
   {
     /* [878] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[108],
   },
   {
     /* [879] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[108],
   },
   {
     /* [880] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[110],
   },
   {
     /* [881] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[110],
   },
   {
     /* [882] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[38],
   },
   {
     /* [883] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[108],
+    /* matcher indices */ &kMatcherIndices[36],
   },
   {
     /* [884] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[110],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [885] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[110],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [886] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[38],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [887] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[36],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [888] */
@@ -7511,37 +7511,37 @@
   {
     /* [904] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[14],
   },
   {
     /* [905] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [906] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[30],
   },
   {
     /* [907] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[101],
   },
   {
     /* [908] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[14],
+    /* matcher indices */ &kMatcherIndices[101],
   },
   {
     /* [909] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[101],
   },
   {
     /* [910] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[101],
   },
   {
     /* [911] */
@@ -7550,238 +7550,238 @@
   },
   {
     /* [912] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[101],
+    /* usage */ ParameterUsage::kTexture,
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [913] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[101],
+    /* usage */ ParameterUsage::kTexture,
+    /* matcher indices */ &kMatcherIndices[114],
   },
   {
     /* [914] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[101],
+    /* usage */ ParameterUsage::kTexture,
+    /* matcher indices */ &kMatcherIndices[118],
   },
   {
     /* [915] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[101],
+    /* usage */ ParameterUsage::kTexture,
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [916] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[112],
+    /* matcher indices */ &kMatcherIndices[124],
   },
   {
     /* [917] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[126],
   },
   {
     /* [918] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[118],
+    /* matcher indices */ &kMatcherIndices[128],
   },
   {
     /* [919] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[120],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [920] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [921] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[126],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [922] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[128],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [923] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[230],
+    /* matcher indices */ &kMatcherIndices[234],
   },
   {
     /* [924] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[231],
+    /* matcher indices */ &kMatcherIndices[48],
   },
   {
     /* [925] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[232],
+    /* matcher indices */ &kMatcherIndices[51],
   },
   {
     /* [926] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[233],
+    /* matcher indices */ &kMatcherIndices[54],
   },
   {
     /* [927] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[234],
+    /* matcher indices */ &kMatcherIndices[57],
   },
   {
     /* [928] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[48],
+    /* matcher indices */ &kMatcherIndices[235],
   },
   {
     /* [929] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[51],
+    /* matcher indices */ &kMatcherIndices[118],
   },
   {
     /* [930] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[54],
+    /* matcher indices */ &kMatcherIndices[126],
   },
   {
     /* [931] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[57],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [932] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[235],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [933] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[118],
+    /* matcher indices */ &kMatcherIndices[54],
   },
   {
     /* [934] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[126],
+    /* matcher indices */ &kMatcherIndices[112],
   },
   {
     /* [935] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[231],
+    /* matcher indices */ &kMatcherIndices[114],
   },
   {
     /* [936] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[233],
+    /* matcher indices */ &kMatcherIndices[118],
   },
   {
     /* [937] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[54],
+    /* matcher indices */ &kMatcherIndices[120],
   },
   {
     /* [938] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[112],
+    /* matcher indices */ &kMatcherIndices[124],
   },
   {
     /* [939] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[114],
+    /* matcher indices */ &kMatcherIndices[126],
   },
   {
     /* [940] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[118],
+    /* matcher indices */ &kMatcherIndices[230],
   },
   {
     /* [941] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[120],
+    /* matcher indices */ &kMatcherIndices[231],
   },
   {
     /* [942] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[124],
+    /* matcher indices */ &kMatcherIndices[232],
   },
   {
     /* [943] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[126],
+    /* matcher indices */ &kMatcherIndices[233],
   },
   {
     /* [944] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[230],
+    /* matcher indices */ &kMatcherIndices[128],
   },
   {
     /* [945] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[231],
-  },
-  {
-    /* [946] */
-    /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[232],
-  },
-  {
-    /* [947] */
-    /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[233],
-  },
-  {
-    /* [948] */
-    /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[128],
-  },
-  {
-    /* [949] */
-    /* usage */ ParameterUsage::kTexture,
     /* matcher indices */ &kMatcherIndices[234],
   },
   {
-    /* [950] */
+    /* [946] */
     /* usage */ ParameterUsage::kNone,
     /* matcher indices */ &kMatcherIndices[5],
   },
   {
-    /* [951] */
+    /* [947] */
     /* usage */ ParameterUsage::kNone,
     /* matcher indices */ &kMatcherIndices[3],
   },
   {
-    /* [952] */
+    /* [948] */
     /* usage */ ParameterUsage::kNone,
     /* matcher indices */ &kMatcherIndices[35],
   },
   {
-    /* [953] */
+    /* [949] */
     /* usage */ ParameterUsage::kNone,
     /* matcher indices */ &kMatcherIndices[33],
   },
   {
-    /* [954] */
+    /* [950] */
     /* usage */ ParameterUsage::kNone,
     /* matcher indices */ &kMatcherIndices[3],
   },
   {
+    /* [951] */
+    /* usage */ ParameterUsage::kNone,
+    /* matcher indices */ &kMatcherIndices[30],
+  },
+  {
+    /* [952] */
+    /* usage */ ParameterUsage::kNone,
+    /* matcher indices */ &kMatcherIndices[3],
+  },
+  {
+    /* [953] */
+    /* usage */ ParameterUsage::kNone,
+    /* matcher indices */ &kMatcherIndices[30],
+  },
+  {
+    /* [954] */
+    /* usage */ ParameterUsage::kNone,
+    /* matcher indices */ &kMatcherIndices[9],
+  },
+  {
     /* [955] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [956] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[101],
   },
   {
     /* [957] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[30],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [958] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[9],
+    /* matcher indices */ &kMatcherIndices[38],
   },
   {
     /* [959] */
@@ -7791,7 +7791,7 @@
   {
     /* [960] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[101],
+    /* matcher indices */ &kMatcherIndices[1],
   },
   {
     /* [961] */
@@ -7801,7 +7801,7 @@
   {
     /* [962] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[38],
+    /* matcher indices */ &kMatcherIndices[35],
   },
   {
     /* [963] */
@@ -7811,7 +7811,7 @@
   {
     /* [964] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[1],
+    /* matcher indices */ &kMatcherIndices[150],
   },
   {
     /* [965] */
@@ -7821,22 +7821,22 @@
   {
     /* [966] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[35],
+    /* matcher indices */ &kMatcherIndices[156],
   },
   {
     /* [967] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[156],
   },
   {
     /* [968] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[150],
+    /* matcher indices */ &kMatcherIndices[156],
   },
   {
     /* [969] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[156],
   },
   {
     /* [970] */
@@ -7846,32 +7846,32 @@
   {
     /* [971] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[156],
+    /* matcher indices */ &kMatcherIndices[102],
   },
   {
     /* [972] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[156],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [973] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[156],
+    /* matcher indices */ &kMatcherIndices[158],
   },
   {
     /* [974] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[156],
+    /* matcher indices */ &kMatcherIndices[158],
   },
   {
     /* [975] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[102],
+    /* matcher indices */ &kMatcherIndices[158],
   },
   {
     /* [976] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[158],
   },
   {
     /* [977] */
@@ -7881,32 +7881,32 @@
   {
     /* [978] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[158],
+    /* matcher indices */ &kMatcherIndices[130],
   },
   {
     /* [979] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[158],
+    /* matcher indices */ &kMatcherIndices[3],
   },
   {
     /* [980] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[158],
+    /* matcher indices */ &kMatcherIndices[170],
   },
   {
     /* [981] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[158],
+    /* matcher indices */ &kMatcherIndices[170],
   },
   {
     /* [982] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[130],
+    /* matcher indices */ &kMatcherIndices[170],
   },
   {
     /* [983] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[3],
+    /* matcher indices */ &kMatcherIndices[170],
   },
   {
     /* [984] */
@@ -7916,155 +7916,135 @@
   {
     /* [985] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[170],
+    /* matcher indices */ &kMatcherIndices[176],
   },
   {
     /* [986] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[170],
+    /* matcher indices */ &kMatcherIndices[178],
   },
   {
     /* [987] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[170],
+    /* matcher indices */ &kMatcherIndices[180],
   },
   {
     /* [988] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[170],
+    /* matcher indices */ &kMatcherIndices[182],
   },
   {
     /* [989] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[176],
+    /* matcher indices */ &kMatcherIndices[184],
   },
   {
     /* [990] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[178],
+    /* matcher indices */ &kMatcherIndices[186],
   },
   {
     /* [991] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[180],
+    /* matcher indices */ &kMatcherIndices[188],
   },
   {
     /* [992] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[182],
+    /* matcher indices */ &kMatcherIndices[190],
   },
   {
     /* [993] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[184],
+    /* matcher indices */ &kMatcherIndices[192],
   },
   {
     /* [994] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[186],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [995] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[188],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [996] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[190],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [997] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[200],
   },
   {
     /* [998] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[202],
   },
   {
     /* [999] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[204],
   },
   {
     /* [1000] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[198],
+    /* matcher indices */ &kMatcherIndices[206],
   },
   {
     /* [1001] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[200],
+    /* matcher indices */ &kMatcherIndices[208],
   },
   {
     /* [1002] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[202],
+    /* matcher indices */ &kMatcherIndices[210],
   },
   {
     /* [1003] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[204],
+    /* matcher indices */ &kMatcherIndices[212],
   },
   {
     /* [1004] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[206],
+    /* matcher indices */ &kMatcherIndices[214],
   },
   {
     /* [1005] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[208],
+    /* matcher indices */ &kMatcherIndices[216],
   },
   {
     /* [1006] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[210],
+    /* matcher indices */ &kMatcherIndices[218],
   },
   {
     /* [1007] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[212],
+    /* matcher indices */ &kMatcherIndices[220],
   },
   {
     /* [1008] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[214],
+    /* matcher indices */ &kMatcherIndices[222],
   },
   {
     /* [1009] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[216],
+    /* matcher indices */ &kMatcherIndices[224],
   },
   {
     /* [1010] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[218],
-  },
-  {
-    /* [1011] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[220],
-  },
-  {
-    /* [1012] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[222],
-  },
-  {
-    /* [1013] */
-    /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[224],
-  },
-  {
-    /* [1014] */
-    /* usage */ ParameterUsage::kNone,
     /* matcher indices */ &kMatcherIndices[226],
   },
   {
-    /* [1015] */
+    /* [1011] */
     /* usage */ ParameterUsage::kNone,
     /* matcher indices */ &kMatcherIndices[228],
   },
@@ -8229,35 +8209,30 @@
   {
     /* [31] */
     /* name */ "T",
-    /* matcher index */ 5,
+    /* matcher index */ 56,
   },
   {
     /* [32] */
     /* name */ "T",
-    /* matcher index */ 56,
+    /* matcher index */ 57,
   },
   {
     /* [33] */
     /* name */ "T",
-    /* matcher index */ 57,
+    /* matcher index */ 54,
   },
   {
     /* [34] */
     /* name */ "T",
-    /* matcher index */ 54,
+    /* matcher index */ 55,
   },
   {
     /* [35] */
     /* name */ "T",
-    /* matcher index */ 55,
-  },
-  {
-    /* [36] */
-    /* name */ "T",
     /* matcher index */ 58,
   },
   {
-    /* [37] */
+    /* [36] */
     /* name */ "T",
     /* matcher index */ 53,
   },
@@ -8324,7 +8299,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[916],
+    /* parameters */ &kParameters[912],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8348,7 +8323,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[917],
+    /* parameters */ &kParameters[913],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8372,7 +8347,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[918],
+    /* parameters */ &kParameters[914],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8396,7 +8371,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[919],
+    /* parameters */ &kParameters[915],
     /* return matcher indices */ &kMatcherIndices[122],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8420,7 +8395,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[920],
+    /* parameters */ &kParameters[916],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8444,7 +8419,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[921],
+    /* parameters */ &kParameters[917],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8468,7 +8443,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[922],
+    /* parameters */ &kParameters[918],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8478,9 +8453,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[923],
+    /* parameters */ &kParameters[919],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8502,9 +8477,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[924],
+    /* parameters */ &kParameters[920],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8526,9 +8501,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[925],
+    /* parameters */ &kParameters[921],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8550,9 +8525,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[926],
+    /* parameters */ &kParameters[922],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8574,9 +8549,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[927],
+    /* parameters */ &kParameters[923],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8586,9 +8561,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 2,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[6],
-    /* parameters */ &kParameters[928],
+    /* parameters */ &kParameters[924],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8598,9 +8573,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 2,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[6],
-    /* parameters */ &kParameters[929],
+    /* parameters */ &kParameters[925],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8610,9 +8585,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 2,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[6],
-    /* parameters */ &kParameters[930],
+    /* parameters */ &kParameters[926],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8622,9 +8597,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 2,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[6],
-    /* parameters */ &kParameters[931],
+    /* parameters */ &kParameters[927],
     /* return matcher indices */ &kMatcherIndices[122],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8634,9 +8609,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[932],
+    /* parameters */ &kParameters[928],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -8646,7 +8621,7 @@
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[501],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -8658,7 +8633,7 @@
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[504],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -8670,7 +8645,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[331],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -8706,7 +8681,7 @@
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[507],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -8718,7 +8693,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[339],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -8730,7 +8705,7 @@
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[510],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -8754,7 +8729,7 @@
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[513],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -8766,7 +8741,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[347],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -8802,7 +8777,7 @@
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[516],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -8826,9 +8801,9 @@
     /* num parameters */ 0,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[37],
+    /* template types */ &kTemplateTypes[36],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -8840,7 +8815,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[982],
+    /* parameters */ &kParameters[978],
     /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -8852,7 +8827,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[983],
+    /* parameters */ &kParameters[979],
     /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecSplat,
@@ -8912,7 +8887,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[792],
+    /* parameters */ &kParameters[788],
     /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecInitM,
@@ -8924,7 +8899,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[794],
+    /* parameters */ &kParameters[790],
     /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecInitM,
@@ -8936,7 +8911,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[796],
+    /* parameters */ &kParameters[792],
     /* return matcher indices */ &kMatcherIndices[130],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecInitM,
@@ -8948,7 +8923,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[984],
+    /* parameters */ &kParameters[980],
     /* return matcher indices */ &kMatcherIndices[110],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -8960,7 +8935,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[985],
+    /* parameters */ &kParameters[981],
     /* return matcher indices */ &kMatcherIndices[172],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -8972,7 +8947,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[986],
+    /* parameters */ &kParameters[982],
     /* return matcher indices */ &kMatcherIndices[152],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -8984,7 +8959,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[987],
+    /* parameters */ &kParameters[983],
     /* return matcher indices */ &kMatcherIndices[154],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -8996,7 +8971,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[988],
+    /* parameters */ &kParameters[984],
     /* return matcher indices */ &kMatcherIndices[174],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -9006,7 +8981,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[387],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -9018,7 +8993,7 @@
     /* num parameters */ 5,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[260],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -9054,7 +9029,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[391],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -9066,7 +9041,7 @@
     /* num parameters */ 5,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[270],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -9078,7 +9053,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[395],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -9246,7 +9221,7 @@
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[495],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -9258,7 +9233,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[311],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -9294,7 +9269,7 @@
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[498],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -9450,7 +9425,7 @@
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[549],
     /* return matcher indices */ nullptr,
@@ -9462,9 +9437,9 @@
     /* num parameters */ 0,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[37],
+    /* template types */ &kTemplateTypes[36],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[102],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -9476,7 +9451,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[975],
+    /* parameters */ &kParameters[971],
     /* return matcher indices */ &kMatcherIndices[102],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -9488,7 +9463,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[976],
+    /* parameters */ &kParameters[972],
     /* return matcher indices */ &kMatcherIndices[102],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecSplat,
@@ -9512,7 +9487,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[788],
+    /* parameters */ &kParameters[784],
     /* return matcher indices */ &kMatcherIndices[102],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecInitM,
@@ -9524,7 +9499,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[790],
+    /* parameters */ &kParameters[786],
     /* return matcher indices */ &kMatcherIndices[102],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecInitM,
@@ -9536,7 +9511,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[977],
+    /* parameters */ &kParameters[973],
     /* return matcher indices */ &kMatcherIndices[134],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -9548,7 +9523,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[978],
+    /* parameters */ &kParameters[974],
     /* return matcher indices */ &kMatcherIndices[166],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -9560,7 +9535,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[979],
+    /* parameters */ &kParameters[975],
     /* return matcher indices */ &kMatcherIndices[144],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -9572,7 +9547,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[980],
+    /* parameters */ &kParameters[976],
     /* return matcher indices */ &kMatcherIndices[122],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -9584,7 +9559,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[981],
+    /* parameters */ &kParameters[977],
     /* return matcher indices */ &kMatcherIndices[168],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -9596,7 +9571,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[938],
+    /* parameters */ &kParameters[934],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -9608,7 +9583,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[939],
+    /* parameters */ &kParameters[935],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -9620,7 +9595,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[940],
+    /* parameters */ &kParameters[936],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -9632,7 +9607,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[941],
+    /* parameters */ &kParameters[937],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -9644,7 +9619,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[942],
+    /* parameters */ &kParameters[938],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -9656,7 +9631,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[943],
+    /* parameters */ &kParameters[939],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -9666,9 +9641,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[944],
+    /* parameters */ &kParameters[940],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -9678,9 +9653,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[945],
+    /* parameters */ &kParameters[941],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -9690,9 +9665,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[946],
+    /* parameters */ &kParameters[942],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -9702,9 +9677,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[947],
+    /* parameters */ &kParameters[943],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -9930,9 +9905,9 @@
     /* num parameters */ 0,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[37],
+    /* template types */ &kTemplateTypes[36],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[150],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -9944,7 +9919,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[968],
+    /* parameters */ &kParameters[964],
     /* return matcher indices */ &kMatcherIndices[150],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -9956,7 +9931,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[969],
+    /* parameters */ &kParameters[965],
     /* return matcher indices */ &kMatcherIndices[150],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecSplat,
@@ -9968,7 +9943,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[27],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[786],
+    /* parameters */ &kParameters[782],
     /* return matcher indices */ &kMatcherIndices[150],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::VecInitS,
@@ -9980,7 +9955,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[970],
+    /* parameters */ &kParameters[966],
     /* return matcher indices */ &kMatcherIndices[108],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -9992,7 +9967,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[971],
+    /* parameters */ &kParameters[967],
     /* return matcher indices */ &kMatcherIndices[162],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10004,7 +9979,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[16],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[972],
+    /* parameters */ &kParameters[968],
     /* return matcher indices */ &kMatcherIndices[132],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10016,7 +9991,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[18],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[973],
+    /* parameters */ &kParameters[969],
     /* return matcher indices */ &kMatcherIndices[116],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10028,7 +10003,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[974],
+    /* parameters */ &kParameters[970],
     /* return matcher indices */ &kMatcherIndices[164],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10038,7 +10013,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[359],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10050,7 +10025,7 @@
     /* num parameters */ 5,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[195],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10086,7 +10061,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[363],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10098,7 +10073,7 @@
     /* num parameters */ 5,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[205],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10110,7 +10085,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[367],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10134,7 +10109,7 @@
     /* num parameters */ 5,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[245],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10146,7 +10121,7 @@
     /* num parameters */ 6,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[102],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10182,7 +10157,7 @@
     /* num parameters */ 5,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[250],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10194,7 +10169,7 @@
     /* num parameters */ 6,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[114],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10206,7 +10181,7 @@
     /* num parameters */ 5,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[255],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10230,7 +10205,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[323],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10242,7 +10217,7 @@
     /* num parameters */ 5,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[170],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10278,7 +10253,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[327],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -10302,7 +10277,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[371],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -10314,7 +10289,7 @@
     /* num parameters */ 5,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[215],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -10350,7 +10325,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[375],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -10374,7 +10349,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[379],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -10386,7 +10361,7 @@
     /* num parameters */ 5,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[230],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -10422,7 +10397,7 @@
     /* num parameters */ 4,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[383],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -10448,7 +10423,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[176],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -10460,7 +10435,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[989],
+    /* parameters */ &kParameters[985],
     /* return matcher indices */ &kMatcherIndices[176],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -10484,7 +10459,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[798],
+    /* parameters */ &kParameters[794],
     /* return matcher indices */ &kMatcherIndices[176],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatInitV,
@@ -10496,7 +10471,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[990],
+    /* parameters */ &kParameters[986],
     /* return matcher indices */ &kMatcherIndices[180],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10508,7 +10483,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[991],
+    /* parameters */ &kParameters[987],
     /* return matcher indices */ &kMatcherIndices[178],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10520,7 +10495,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[182],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -10532,7 +10507,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[992],
+    /* parameters */ &kParameters[988],
     /* return matcher indices */ &kMatcherIndices[182],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -10556,7 +10531,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[800],
+    /* parameters */ &kParameters[796],
     /* return matcher indices */ &kMatcherIndices[182],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatInitV,
@@ -10568,7 +10543,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[993],
+    /* parameters */ &kParameters[989],
     /* return matcher indices */ &kMatcherIndices[186],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10580,7 +10555,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[994],
+    /* parameters */ &kParameters[990],
     /* return matcher indices */ &kMatcherIndices[184],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10592,7 +10567,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[188],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -10604,7 +10579,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[995],
+    /* parameters */ &kParameters[991],
     /* return matcher indices */ &kMatcherIndices[188],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -10628,7 +10603,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[802],
+    /* parameters */ &kParameters[798],
     /* return matcher indices */ &kMatcherIndices[188],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::MatInitV,
@@ -10640,7 +10615,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[996],
+    /* parameters */ &kParameters[992],
     /* return matcher indices */ &kMatcherIndices[192],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10652,7 +10627,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[997],
+    /* parameters */ &kParameters[993],
     /* return matcher indices */ &kMatcherIndices[190],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10664,7 +10639,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[194],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -10676,7 +10651,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[998],
+    /* parameters */ &kParameters[994],
     /* return matcher indices */ &kMatcherIndices[194],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -10712,7 +10687,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[999],
+    /* parameters */ &kParameters[995],
     /* return matcher indices */ &kMatcherIndices[198],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10724,7 +10699,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1000],
+    /* parameters */ &kParameters[996],
     /* return matcher indices */ &kMatcherIndices[196],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10736,7 +10711,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[200],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -10748,7 +10723,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1001],
+    /* parameters */ &kParameters[997],
     /* return matcher indices */ &kMatcherIndices[200],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -10784,7 +10759,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1002],
+    /* parameters */ &kParameters[998],
     /* return matcher indices */ &kMatcherIndices[204],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10796,7 +10771,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1003],
+    /* parameters */ &kParameters[999],
     /* return matcher indices */ &kMatcherIndices[202],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10808,7 +10783,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[206],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -10820,7 +10795,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1004],
+    /* parameters */ &kParameters[1000],
     /* return matcher indices */ &kMatcherIndices[206],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -10856,7 +10831,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1005],
+    /* parameters */ &kParameters[1001],
     /* return matcher indices */ &kMatcherIndices[210],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10868,7 +10843,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1006],
+    /* parameters */ &kParameters[1002],
     /* return matcher indices */ &kMatcherIndices[208],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10880,7 +10855,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[212],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -10892,7 +10867,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1007],
+    /* parameters */ &kParameters[1003],
     /* return matcher indices */ &kMatcherIndices[212],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -10928,7 +10903,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1008],
+    /* parameters */ &kParameters[1004],
     /* return matcher indices */ &kMatcherIndices[216],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10940,7 +10915,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1009],
+    /* parameters */ &kParameters[1005],
     /* return matcher indices */ &kMatcherIndices[214],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -10952,7 +10927,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[218],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -10964,7 +10939,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1010],
+    /* parameters */ &kParameters[1006],
     /* return matcher indices */ &kMatcherIndices[218],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -11000,7 +10975,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1011],
+    /* parameters */ &kParameters[1007],
     /* return matcher indices */ &kMatcherIndices[222],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -11012,7 +10987,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1012],
+    /* parameters */ &kParameters[1008],
     /* return matcher indices */ &kMatcherIndices[220],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -11024,7 +10999,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[224],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
@@ -11036,7 +11011,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1013],
+    /* parameters */ &kParameters[1009],
     /* return matcher indices */ &kMatcherIndices[224],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
@@ -11072,7 +11047,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[14],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1014],
+    /* parameters */ &kParameters[1010],
     /* return matcher indices */ &kMatcherIndices[228],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -11084,7 +11059,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[12],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1015],
+    /* parameters */ &kParameters[1011],
     /* return matcher indices */ &kMatcherIndices[226],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
@@ -11096,7 +11071,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[933],
+    /* parameters */ &kParameters[929],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -11108,7 +11083,7 @@
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[934],
+    /* parameters */ &kParameters[930],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -11118,9 +11093,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[935],
+    /* parameters */ &kParameters[931],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -11130,9 +11105,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[936],
+    /* parameters */ &kParameters[932],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -11142,9 +11117,9 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 2,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[6],
-    /* parameters */ &kParameters[937],
+    /* parameters */ &kParameters[933],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
@@ -11322,55 +11297,55 @@
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[30],
+    /* template types */ &kTemplateTypes[22],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[718],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpModulo,
   },
   {
     /* [251] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[30],
+    /* template types */ &kTemplateTypes[22],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[720],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpModulo,
   },
   {
     /* [252] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[30],
+    /* template types */ &kTemplateTypes[22],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[722],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpModulo,
   },
   {
     /* [253] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[30],
+    /* template types */ &kTemplateTypes[22],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[724],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
+    /* const eval */ &ConstEval::OpModulo,
   },
   {
     /* [254] */
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[730],
     /* return matcher indices */ &kMatcherIndices[35],
@@ -11382,7 +11357,7 @@
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[732],
     /* return matcher indices */ &kMatcherIndices[33],
@@ -11418,7 +11393,7 @@
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[738],
     /* return matcher indices */ &kMatcherIndices[35],
@@ -11430,7 +11405,7 @@
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[740],
     /* return matcher indices */ &kMatcherIndices[33],
@@ -11463,54 +11438,6 @@
   },
   {
     /* [262] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[25],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[774],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::OpShiftLeft,
-  },
-  {
-    /* [263] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[25],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[776],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::OpShiftLeft,
-  },
-  {
-    /* [264] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[31],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[778],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::OpShiftLeft,
-  },
-  {
-    /* [265] */
-    /* num parameters */ 2,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[31],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[780],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::OpShiftLeft,
-  },
-  {
-    /* [266] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11522,7 +11449,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [267] */
+    /* [263] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11534,7 +11461,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [268] */
+    /* [264] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11546,7 +11473,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [269] */
+    /* [265] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11558,7 +11485,7 @@
     /* const eval */ &ConstEval::select_bool,
   },
   {
-    /* [270] */
+    /* [266] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11570,7 +11497,7 @@
     /* const eval */ &ConstEval::select_bool,
   },
   {
-    /* [271] */
+    /* [267] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11582,304 +11509,352 @@
     /* const eval */ &ConstEval::select_boolvec,
   },
   {
-    /* [272] */
+    /* [268] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ &kMatcherIndices[9],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
   {
-    /* [273] */
+    /* [269] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[958],
+    /* parameters */ &kParameters[954],
     /* return matcher indices */ &kMatcherIndices[9],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
   {
-    /* [274] */
+    /* [270] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[31],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[955],
+    /* return matcher indices */ &kMatcherIndices[9],
+    /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::Conv,
+  },
+  {
+    /* [271] */
+    /* num parameters */ 0,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[1012],
+    /* return matcher indices */ &kMatcherIndices[101],
+    /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::Zero,
+  },
+  {
+    /* [272] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[956],
+    /* return matcher indices */ &kMatcherIndices[101],
+    /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::Identity,
+  },
+  {
+    /* [273] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[32],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[959],
-    /* return matcher indices */ &kMatcherIndices[9],
+    /* parameters */ &kParameters[957],
+    /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
   {
-    /* [275] */
+    /* [274] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
-    /* return matcher indices */ &kMatcherIndices[101],
+    /* parameters */ &kParameters[1012],
+    /* return matcher indices */ &kMatcherIndices[38],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
   {
-    /* [276] */
+    /* [275] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[960],
-    /* return matcher indices */ &kMatcherIndices[101],
+    /* parameters */ &kParameters[958],
+    /* return matcher indices */ &kMatcherIndices[38],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
   {
-    /* [277] */
+    /* [276] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[33],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[961],
-    /* return matcher indices */ &kMatcherIndices[101],
+    /* parameters */ &kParameters[959],
+    /* return matcher indices */ &kMatcherIndices[38],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
   {
-    /* [278] */
+    /* [277] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
-    /* return matcher indices */ &kMatcherIndices[38],
+    /* parameters */ &kParameters[1012],
+    /* return matcher indices */ &kMatcherIndices[1],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
   {
-    /* [279] */
+    /* [278] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[962],
-    /* return matcher indices */ &kMatcherIndices[38],
+    /* parameters */ &kParameters[960],
+    /* return matcher indices */ &kMatcherIndices[1],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
   {
-    /* [280] */
+    /* [279] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[34],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[963],
-    /* return matcher indices */ &kMatcherIndices[38],
+    /* parameters */ &kParameters[961],
+    /* return matcher indices */ &kMatcherIndices[1],
     /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Conv,
   },
   {
-    /* [281] */
+    /* [280] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
-    /* return matcher indices */ &kMatcherIndices[1],
+    /* parameters */ &kParameters[1012],
+    /* return matcher indices */ &kMatcherIndices[35],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Zero,
   },
   {
-    /* [282] */
+    /* [281] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[964],
-    /* return matcher indices */ &kMatcherIndices[1],
+    /* parameters */ &kParameters[962],
+    /* return matcher indices */ &kMatcherIndices[35],
     /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
   {
+    /* [282] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[35],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[963],
+    /* return matcher indices */ &kMatcherIndices[35],
+    /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::Conv,
+  },
+  {
     /* [283] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[35],
+    /* template types */ &kTemplateTypes[22],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[965],
-    /* return matcher indices */ &kMatcherIndices[1],
-    /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::Conv,
+    /* parameters */ &kParameters[800],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::abs,
   },
   {
     /* [284] */
-    /* num parameters */ 0,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
-    /* return matcher indices */ &kMatcherIndices[35],
-    /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::Zero,
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[22],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[801],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::abs,
   },
   {
     /* [285] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[966],
-    /* return matcher indices */ &kMatcherIndices[35],
-    /* flags */ OverloadFlags(OverloadFlag::kIsInitializer, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::Identity,
+    /* parameters */ &kParameters[802],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::acos,
   },
   {
     /* [286] */
     /* num parameters */ 1,
     /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[36],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[967],
-    /* return matcher indices */ &kMatcherIndices[35],
-    /* flags */ OverloadFlags(OverloadFlag::kIsConverter, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::Conv,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[803],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::acos,
   },
   {
     /* [287] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[22],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[804],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::abs,
+    /* const eval */ &ConstEval::acosh,
   },
   {
     /* [288] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[22],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[805],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::abs,
+    /* const eval */ &ConstEval::acosh,
   },
   {
     /* [289] */
     /* num parameters */ 1,
-    /* num template types */ 1,
+    /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[806],
-    /* return matcher indices */ &kMatcherIndices[3],
+    /* return matcher indices */ &kMatcherIndices[35],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::acos,
+    /* const eval */ &ConstEval::all,
   },
   {
     /* [290] */
     /* num parameters */ 1,
-    /* num template types */ 1,
+    /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[807],
-    /* return matcher indices */ &kMatcherIndices[30],
+    /* return matcher indices */ &kMatcherIndices[35],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::acos,
+    /* const eval */ &ConstEval::all,
   },
   {
     /* [291] */
     /* num parameters */ 1,
-    /* num template types */ 1,
+    /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[808],
-    /* return matcher indices */ &kMatcherIndices[3],
+    /* return matcher indices */ &kMatcherIndices[35],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::acosh,
+    /* const eval */ &ConstEval::any,
   },
   {
     /* [292] */
     /* num parameters */ 1,
-    /* num template types */ 1,
+    /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[809],
-    /* return matcher indices */ &kMatcherIndices[30],
+    /* return matcher indices */ &kMatcherIndices[35],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::acosh,
+    /* const eval */ &ConstEval::any,
   },
   {
     /* [293] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[810],
-    /* return matcher indices */ &kMatcherIndices[35],
+    /* parameters */ &kParameters[811],
+    /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::all,
+    /* const eval */ &ConstEval::asin,
   },
   {
     /* [294] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[811],
-    /* return matcher indices */ &kMatcherIndices[35],
+    /* parameters */ &kParameters[812],
+    /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::all,
+    /* const eval */ &ConstEval::asin,
   },
   {
     /* [295] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[812],
-    /* return matcher indices */ &kMatcherIndices[35],
+    /* parameters */ &kParameters[813],
+    /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::any,
+    /* const eval */ &ConstEval::asinh,
   },
   {
     /* [296] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[813],
-    /* return matcher indices */ &kMatcherIndices[35],
+    /* parameters */ &kParameters[814],
+    /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::any,
+    /* const eval */ &ConstEval::asinh,
   },
   {
     /* [297] */
@@ -11891,7 +11866,7 @@
     /* parameters */ &kParameters[815],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::asin,
+    /* const eval */ &ConstEval::atan,
   },
   {
     /* [298] */
@@ -11903,58 +11878,10 @@
     /* parameters */ &kParameters[816],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::asin,
+    /* const eval */ &ConstEval::atan,
   },
   {
     /* [299] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[817],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::asinh,
-  },
-  {
-    /* [300] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[818],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::asinh,
-  },
-  {
-    /* [301] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[819],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::atan,
-  },
-  {
-    /* [302] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[820],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::atan,
-  },
-  {
-    /* [303] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -11966,7 +11893,7 @@
     /* const eval */ &ConstEval::atan2,
   },
   {
-    /* [304] */
+    /* [300] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -11978,55 +11905,55 @@
     /* const eval */ &ConstEval::atan2,
   },
   {
+    /* [301] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[817],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::atanh,
+  },
+  {
+    /* [302] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[818],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::atanh,
+  },
+  {
+    /* [303] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[819],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::ceil,
+  },
+  {
+    /* [304] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[820],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::ceil,
+  },
+  {
     /* [305] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[821],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::atanh,
-  },
-  {
-    /* [306] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[822],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::atanh,
-  },
-  {
-    /* [307] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[823],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::ceil,
-  },
-  {
-    /* [308] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[824],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::ceil,
-  },
-  {
-    /* [309] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12038,7 +11965,7 @@
     /* const eval */ &ConstEval::clamp,
   },
   {
-    /* [310] */
+    /* [306] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12050,52 +11977,100 @@
     /* const eval */ &ConstEval::clamp,
   },
   {
-    /* [311] */
+    /* [307] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[825],
+    /* parameters */ &kParameters[821],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::cos,
   },
   {
+    /* [308] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[822],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::cos,
+  },
+  {
+    /* [309] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[823],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::cosh,
+  },
+  {
+    /* [310] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[824],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::cosh,
+  },
+  {
+    /* [311] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[25],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[825],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::countLeadingZeros,
+  },
+  {
     /* [312] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[25],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[826],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::cos,
+    /* const eval */ &ConstEval::countLeadingZeros,
   },
   {
     /* [313] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[25],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[827],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::cosh,
+    /* const eval */ &ConstEval::countOneBits,
   },
   {
     /* [314] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[25],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[828],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::cosh,
+    /* const eval */ &ConstEval::countOneBits,
   },
   {
     /* [315] */
@@ -12107,7 +12082,7 @@
     /* parameters */ &kParameters[829],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::countLeadingZeros,
+    /* const eval */ &ConstEval::countTrailingZeros,
   },
   {
     /* [316] */
@@ -12119,82 +12094,34 @@
     /* parameters */ &kParameters[830],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::countLeadingZeros,
+    /* const eval */ &ConstEval::countTrailingZeros,
   },
   {
     /* [317] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[25],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[831],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::countOneBits,
+    /* const eval */ &ConstEval::degrees,
   },
   {
     /* [318] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[25],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[832],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::countOneBits,
+    /* const eval */ &ConstEval::degrees,
   },
   {
     /* [319] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[25],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[833],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::countTrailingZeros,
-  },
-  {
-    /* [320] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[25],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[834],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::countTrailingZeros,
-  },
-  {
-    /* [321] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[835],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::degrees,
-  },
-  {
-    /* [322] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[836],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::degrees,
-  },
-  {
-    /* [323] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12206,7 +12133,7 @@
     /* const eval */ &ConstEval::distance,
   },
   {
-    /* [324] */
+    /* [320] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12218,11 +12145,59 @@
     /* const eval */ &ConstEval::distance,
   },
   {
+    /* [321] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[834],
+    /* return matcher indices */ &kMatcherIndices[38],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
+    /* const eval */ nullptr,
+  },
+  {
+    /* [322] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[835],
+    /* return matcher indices */ &kMatcherIndices[36],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
+    /* const eval */ nullptr,
+  },
+  {
+    /* [323] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[836],
+    /* return matcher indices */ &kMatcherIndices[38],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
+    /* const eval */ nullptr,
+  },
+  {
+    /* [324] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[837],
+    /* return matcher indices */ &kMatcherIndices[36],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
+    /* const eval */ nullptr,
+  },
+  {
     /* [325] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[838],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -12234,7 +12209,7 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[839],
     /* return matcher indices */ &kMatcherIndices[36],
@@ -12246,7 +12221,7 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[840],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -12258,7 +12233,7 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[841],
     /* return matcher indices */ &kMatcherIndices[36],
@@ -12270,7 +12245,7 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[842],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -12282,7 +12257,7 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[843],
     /* return matcher indices */ &kMatcherIndices[36],
@@ -12294,7 +12269,7 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[844],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -12306,7 +12281,7 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[845],
     /* return matcher indices */ &kMatcherIndices[36],
@@ -12316,101 +12291,53 @@
   {
     /* [333] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[846],
-    /* return matcher indices */ &kMatcherIndices[38],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
-    /* const eval */ nullptr,
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::exp,
   },
   {
     /* [334] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[847],
-    /* return matcher indices */ &kMatcherIndices[36],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
-    /* const eval */ nullptr,
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::exp,
   },
   {
     /* [335] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[848],
-    /* return matcher indices */ &kMatcherIndices[38],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
-    /* const eval */ nullptr,
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::exp2,
   },
   {
     /* [336] */
     /* num parameters */ 1,
-    /* num template types */ 0,
+    /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[849],
-    /* return matcher indices */ &kMatcherIndices[36],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
-    /* const eval */ nullptr,
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::exp2,
   },
   {
     /* [337] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[850],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::exp,
-  },
-  {
-    /* [338] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[851],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::exp,
-  },
-  {
-    /* [339] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[852],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::exp2,
-  },
-  {
-    /* [340] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[853],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::exp2,
-  },
-  {
-    /* [341] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12422,7 +12349,7 @@
     /* const eval */ &ConstEval::extractBits,
   },
   {
-    /* [342] */
+    /* [338] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12434,79 +12361,79 @@
     /* const eval */ &ConstEval::extractBits,
   },
   {
-    /* [343] */
+    /* [339] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[25],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[854],
+    /* parameters */ &kParameters[850],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::firstLeadingBit,
   },
   {
+    /* [340] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[25],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[851],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::firstLeadingBit,
+  },
+  {
+    /* [341] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[25],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[852],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::firstTrailingBit,
+  },
+  {
+    /* [342] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[25],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[853],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::firstTrailingBit,
+  },
+  {
+    /* [343] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[854],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::floor,
+  },
+  {
     /* [344] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[25],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[855],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::firstLeadingBit,
+    /* const eval */ &ConstEval::floor,
   },
   {
     /* [345] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[25],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[856],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::firstTrailingBit,
-  },
-  {
-    /* [346] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[25],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[857],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::firstTrailingBit,
-  },
-  {
-    /* [347] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[858],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::floor,
-  },
-  {
-    /* [348] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[859],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::floor,
-  },
-  {
-    /* [349] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12518,7 +12445,7 @@
     /* const eval */ &ConstEval::fma,
   },
   {
-    /* [350] */
+    /* [346] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12530,59 +12457,107 @@
     /* const eval */ &ConstEval::fma,
   },
   {
-    /* [351] */
+    /* [347] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[860],
+    /* parameters */ &kParameters[856],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
-    /* [352] */
+    /* [348] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[26],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[861],
+    /* parameters */ &kParameters[857],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
-    /* [353] */
+    /* [349] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[862],
+    /* parameters */ &kParameters[858],
     /* return matcher indices */ &kMatcherIndices[104],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::frexp,
   },
   {
-    /* [354] */
+    /* [350] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[863],
+    /* parameters */ &kParameters[859],
     /* return matcher indices */ &kMatcherIndices[39],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::frexp,
   },
   {
+    /* [351] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[860],
+    /* return matcher indices */ &kMatcherIndices[38],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
+    /* const eval */ nullptr,
+  },
+  {
+    /* [352] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[861],
+    /* return matcher indices */ &kMatcherIndices[36],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
+    /* const eval */ nullptr,
+  },
+  {
+    /* [353] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[862],
+    /* return matcher indices */ &kMatcherIndices[38],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
+    /* const eval */ nullptr,
+  },
+  {
+    /* [354] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[863],
+    /* return matcher indices */ &kMatcherIndices[36],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
+    /* const eval */ nullptr,
+  },
+  {
     /* [355] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[864],
     /* return matcher indices */ &kMatcherIndices[38],
@@ -12594,7 +12569,7 @@
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[865],
     /* return matcher indices */ &kMatcherIndices[36],
@@ -12603,54 +12578,6 @@
   },
   {
     /* [357] */
-    /* num parameters */ 1,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[866],
-    /* return matcher indices */ &kMatcherIndices[38],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
-    /* const eval */ nullptr,
-  },
-  {
-    /* [358] */
-    /* num parameters */ 1,
-    /* num template types */ 0,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[867],
-    /* return matcher indices */ &kMatcherIndices[36],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
-    /* const eval */ nullptr,
-  },
-  {
-    /* [359] */
-    /* num parameters */ 1,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[868],
-    /* return matcher indices */ &kMatcherIndices[38],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
-    /* const eval */ nullptr,
-  },
-  {
-    /* [360] */
-    /* num parameters */ 1,
-    /* num template types */ 0,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[869],
-    /* return matcher indices */ &kMatcherIndices[36],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline),
-    /* const eval */ nullptr,
-  },
-  {
-    /* [361] */
     /* num parameters */ 4,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12662,7 +12589,7 @@
     /* const eval */ &ConstEval::insertBits,
   },
   {
-    /* [362] */
+    /* [358] */
     /* num parameters */ 4,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12674,31 +12601,31 @@
     /* const eval */ &ConstEval::insertBits,
   },
   {
-    /* [363] */
+    /* [359] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[870],
+    /* parameters */ &kParameters[866],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::inverseSqrt,
   },
   {
-    /* [364] */
+    /* [360] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[871],
+    /* parameters */ &kParameters[867],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::inverseSqrt,
   },
   {
-    /* [365] */
+    /* [361] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12710,7 +12637,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [366] */
+    /* [362] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12722,6 +12649,54 @@
     /* const eval */ nullptr,
   },
   {
+    /* [363] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[868],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::length,
+  },
+  {
+    /* [364] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[869],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::length,
+  },
+  {
+    /* [365] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[870],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::log,
+  },
+  {
+    /* [366] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[871],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::log,
+  },
+  {
     /* [367] */
     /* num parameters */ 1,
     /* num template types */ 1,
@@ -12731,7 +12706,7 @@
     /* parameters */ &kParameters[872],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::length,
+    /* const eval */ &ConstEval::log2,
   },
   {
     /* [368] */
@@ -12741,60 +12716,12 @@
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[873],
-    /* return matcher indices */ &kMatcherIndices[3],
+    /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::length,
+    /* const eval */ &ConstEval::log2,
   },
   {
     /* [369] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[874],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::log,
-  },
-  {
-    /* [370] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[875],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::log,
-  },
-  {
-    /* [371] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[876],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::log2,
-  },
-  {
-    /* [372] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[877],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::log2,
-  },
-  {
-    /* [373] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12806,7 +12733,7 @@
     /* const eval */ &ConstEval::max,
   },
   {
-    /* [374] */
+    /* [370] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12818,7 +12745,7 @@
     /* const eval */ &ConstEval::max,
   },
   {
-    /* [375] */
+    /* [371] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12830,7 +12757,7 @@
     /* const eval */ &ConstEval::min,
   },
   {
-    /* [376] */
+    /* [372] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12842,31 +12769,31 @@
     /* const eval */ &ConstEval::min,
   },
   {
-    /* [377] */
+    /* [373] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[878],
+    /* parameters */ &kParameters[874],
     /* return matcher indices */ &kMatcherIndices[106],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::modf,
   },
   {
-    /* [378] */
+    /* [374] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[879],
+    /* parameters */ &kParameters[875],
     /* return matcher indices */ &kMatcherIndices[45],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::modf,
   },
   {
-    /* [379] */
+    /* [375] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -12878,7 +12805,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [380] */
+    /* [376] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -12890,30 +12817,78 @@
     /* const eval */ nullptr,
   },
   {
-    /* [381] */
+    /* [377] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[886],
+    /* parameters */ &kParameters[882],
     /* return matcher indices */ &kMatcherIndices[38],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::quantizeToF16,
   },
   {
-    /* [382] */
+    /* [378] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[887],
+    /* parameters */ &kParameters[883],
     /* return matcher indices */ &kMatcherIndices[36],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::quantizeToF16,
   },
   {
+    /* [379] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[884],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::radians,
+  },
+  {
+    /* [380] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[885],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::radians,
+  },
+  {
+    /* [381] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[25],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[886],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::reverseBits,
+  },
+  {
+    /* [382] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[25],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[887],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::reverseBits,
+  },
+  {
     /* [383] */
     /* num parameters */ 1,
     /* num template types */ 1,
@@ -12923,7 +12898,7 @@
     /* parameters */ &kParameters[888],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::radians,
+    /* const eval */ &ConstEval::round,
   },
   {
     /* [384] */
@@ -12935,31 +12910,31 @@
     /* parameters */ &kParameters[889],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::radians,
+    /* const eval */ &ConstEval::round,
   },
   {
     /* [385] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[25],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[890],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::reverseBits,
+    /* const eval */ &ConstEval::saturate,
   },
   {
     /* [386] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[25],
+    /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
     /* parameters */ &kParameters[891],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::reverseBits,
+    /* const eval */ &ConstEval::saturate,
   },
   {
     /* [387] */
@@ -12971,7 +12946,7 @@
     /* parameters */ &kParameters[892],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::round,
+    /* const eval */ &ConstEval::sign,
   },
   {
     /* [388] */
@@ -12983,7 +12958,7 @@
     /* parameters */ &kParameters[893],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::round,
+    /* const eval */ &ConstEval::sign,
   },
   {
     /* [389] */
@@ -12995,7 +12970,7 @@
     /* parameters */ &kParameters[894],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::saturate,
+    /* const eval */ &ConstEval::sin,
   },
   {
     /* [390] */
@@ -13007,7 +12982,7 @@
     /* parameters */ &kParameters[895],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::saturate,
+    /* const eval */ &ConstEval::sin,
   },
   {
     /* [391] */
@@ -13019,7 +12994,7 @@
     /* parameters */ &kParameters[896],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::sign,
+    /* const eval */ &ConstEval::sinh,
   },
   {
     /* [392] */
@@ -13031,58 +13006,10 @@
     /* parameters */ &kParameters[897],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::sign,
+    /* const eval */ &ConstEval::sinh,
   },
   {
     /* [393] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[898],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::sin,
-  },
-  {
-    /* [394] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[899],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::sin,
-  },
-  {
-    /* [395] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[900],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::sinh,
-  },
-  {
-    /* [396] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[901],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::sinh,
-  },
-  {
-    /* [397] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13094,7 +13021,7 @@
     /* const eval */ &ConstEval::smoothstep,
   },
   {
-    /* [398] */
+    /* [394] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13106,31 +13033,31 @@
     /* const eval */ &ConstEval::smoothstep,
   },
   {
-    /* [399] */
+    /* [395] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[902],
+    /* parameters */ &kParameters[898],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::sqrt,
   },
   {
-    /* [400] */
+    /* [396] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[903],
+    /* parameters */ &kParameters[899],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::sqrt,
   },
   {
-    /* [401] */
+    /* [397] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13142,7 +13069,7 @@
     /* const eval */ &ConstEval::step,
   },
   {
-    /* [402] */
+    /* [398] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13154,16 +13081,64 @@
     /* const eval */ &ConstEval::step,
   },
   {
+    /* [399] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[900],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::tan,
+  },
+  {
+    /* [400] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[901],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::tan,
+  },
+  {
+    /* [401] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[902],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::tanh,
+  },
+  {
+    /* [402] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[23],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[903],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::tanh,
+  },
+  {
     /* [403] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[904],
+    /* parameters */ &kParameters[905],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::tan,
+    /* const eval */ &ConstEval::trunc,
   },
   {
     /* [404] */
@@ -13172,89 +13147,41 @@
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[905],
+    /* parameters */ &kParameters[906],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::tan,
+    /* const eval */ &ConstEval::trunc,
   },
   {
     /* [405] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
+    /* template types */ &kTemplateTypes[0],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[906],
-    /* return matcher indices */ &kMatcherIndices[3],
+    /* parameters */ &kParameters[944],
+    /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::tanh,
+    /* const eval */ nullptr,
   },
   {
     /* [406] */
     /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[907],
-    /* return matcher indices */ &kMatcherIndices[30],
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[945],
+    /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::tanh,
+    /* const eval */ nullptr,
   },
   {
     /* [407] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[909],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::trunc,
-  },
-  {
-    /* [408] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[23],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[910],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::trunc,
-  },
-  {
-    /* [409] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[0],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[948],
-    /* return matcher indices */ &kMatcherIndices[101],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
-  },
-  {
-    /* [410] */
-    /* num parameters */ 1,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[949],
-    /* return matcher indices */ &kMatcherIndices[101],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ nullptr,
-  },
-  {
-    /* [411] */
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[519],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -13262,11 +13189,11 @@
     /* const eval */ nullptr,
   },
   {
-    /* [412] */
+    /* [408] */
     /* num parameters */ 3,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[522],
     /* return matcher indices */ &kMatcherIndices[110],
@@ -13274,79 +13201,79 @@
     /* const eval */ nullptr,
   },
   {
-    /* [413] */
+    /* [409] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[952],
+    /* parameters */ &kParameters[948],
     /* return matcher indices */ &kMatcherIndices[35],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::OpNot,
   },
   {
-    /* [414] */
+    /* [410] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[953],
+    /* parameters */ &kParameters[949],
     /* return matcher indices */ &kMatcherIndices[33],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::OpNot,
   },
   {
+    /* [411] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[28],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[950],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::OpComplement,
+  },
+  {
+    /* [412] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[28],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[951],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::OpComplement,
+  },
+  {
+    /* [413] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[29],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[952],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::OpUnaryMinus,
+  },
+  {
+    /* [414] */
+    /* num parameters */ 1,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[29],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[953],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::OpUnaryMinus,
+  },
+  {
     /* [415] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[28],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[954],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::OpComplement,
-  },
-  {
-    /* [416] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[28],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[955],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::OpComplement,
-  },
-  {
-    /* [417] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[29],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[956],
-    /* return matcher indices */ &kMatcherIndices[3],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::OpUnaryMinus,
-  },
-  {
-    /* [418] */
-    /* num parameters */ 1,
-    /* num template types */ 1,
-    /* num template numbers */ 1,
-    /* template types */ &kTemplateTypes[29],
-    /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[957],
-    /* return matcher indices */ &kMatcherIndices[30],
-    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::OpUnaryMinus,
-  },
-  {
-    /* [419] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13358,7 +13285,7 @@
     /* const eval */ &ConstEval::OpXor,
   },
   {
-    /* [420] */
+    /* [416] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13370,7 +13297,7 @@
     /* const eval */ &ConstEval::OpXor,
   },
   {
-    /* [421] */
+    /* [417] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13382,7 +13309,7 @@
     /* const eval */ &ConstEval::OpEqual,
   },
   {
-    /* [422] */
+    /* [418] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13394,7 +13321,7 @@
     /* const eval */ &ConstEval::OpEqual,
   },
   {
-    /* [423] */
+    /* [419] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13406,7 +13333,7 @@
     /* const eval */ &ConstEval::OpNotEqual,
   },
   {
-    /* [424] */
+    /* [420] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13418,7 +13345,7 @@
     /* const eval */ &ConstEval::OpNotEqual,
   },
   {
-    /* [425] */
+    /* [421] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13430,7 +13357,7 @@
     /* const eval */ &ConstEval::OpLessThan,
   },
   {
-    /* [426] */
+    /* [422] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13442,7 +13369,7 @@
     /* const eval */ &ConstEval::OpLessThan,
   },
   {
-    /* [427] */
+    /* [423] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13454,7 +13381,7 @@
     /* const eval */ &ConstEval::OpGreaterThan,
   },
   {
-    /* [428] */
+    /* [424] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13466,7 +13393,7 @@
     /* const eval */ &ConstEval::OpGreaterThan,
   },
   {
-    /* [429] */
+    /* [425] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13478,7 +13405,7 @@
     /* const eval */ &ConstEval::OpLessThanEqual,
   },
   {
-    /* [430] */
+    /* [426] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13490,7 +13417,7 @@
     /* const eval */ &ConstEval::OpLessThanEqual,
   },
   {
-    /* [431] */
+    /* [427] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13502,7 +13429,7 @@
     /* const eval */ &ConstEval::OpGreaterThanEqual,
   },
   {
-    /* [432] */
+    /* [428] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13514,43 +13441,67 @@
     /* const eval */ &ConstEval::OpGreaterThanEqual,
   },
   {
-    /* [433] */
+    /* [429] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[28],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[774],
+    /* return matcher indices */ &kMatcherIndices[3],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::OpShiftLeft,
+  },
+  {
+    /* [430] */
+    /* num parameters */ 2,
+    /* num template types */ 1,
+    /* num template numbers */ 1,
+    /* template types */ &kTemplateTypes[28],
+    /* template numbers */ &kTemplateNumbers[4],
+    /* parameters */ &kParameters[776],
+    /* return matcher indices */ &kMatcherIndices[30],
+    /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::OpShiftLeft,
+  },
+  {
+    /* [431] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[25],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[782],
+    /* parameters */ &kParameters[778],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
-    /* [434] */
+    /* [432] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[25],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[784],
+    /* parameters */ &kParameters[780],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
-    /* [435] */
+    /* [433] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[24],
     /* template numbers */ &kTemplateNumbers[8],
-    /* parameters */ &kParameters[814],
+    /* parameters */ &kParameters[810],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
-    /* [436] */
+    /* [434] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 0,
@@ -13562,19 +13513,19 @@
     /* const eval */ &ConstEval::cross,
   },
   {
-    /* [437] */
+    /* [435] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[837],
+    /* parameters */ &kParameters[833],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::determinant,
   },
   {
-    /* [438] */
+    /* [436] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13586,11 +13537,11 @@
     /* const eval */ &ConstEval::dot,
   },
   {
-    /* [439] */
+    /* [437] */
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[606],
     /* return matcher indices */ &kMatcherIndices[9],
@@ -13598,11 +13549,11 @@
     /* const eval */ nullptr,
   },
   {
-    /* [440] */
+    /* [438] */
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[608],
     /* return matcher indices */ &kMatcherIndices[101],
@@ -13610,7 +13561,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [441] */
+    /* [439] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13622,79 +13573,79 @@
     /* const eval */ &ConstEval::faceForward,
   },
   {
-    /* [442] */
+    /* [440] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[4],
-    /* parameters */ &kParameters[880],
+    /* parameters */ &kParameters[876],
     /* return matcher indices */ &kMatcherIndices[30],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::normalize,
   },
   {
+    /* [441] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[877],
+    /* return matcher indices */ &kMatcherIndices[101],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::pack2x16float,
+  },
+  {
+    /* [442] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[878],
+    /* return matcher indices */ &kMatcherIndices[101],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::pack2x16snorm,
+  },
+  {
     /* [443] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[881],
+    /* parameters */ &kParameters[879],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::pack2x16float,
+    /* const eval */ &ConstEval::pack2x16unorm,
   },
   {
     /* [444] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[882],
+    /* parameters */ &kParameters[880],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::pack2x16snorm,
+    /* const eval */ &ConstEval::pack4x8snorm,
   },
   {
     /* [445] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[883],
-    /* return matcher indices */ &kMatcherIndices[101],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::pack2x16unorm,
-  },
-  {
-    /* [446] */
-    /* num parameters */ 1,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[884],
-    /* return matcher indices */ &kMatcherIndices[101],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::pack4x8snorm,
-  },
-  {
-    /* [447] */
-    /* num parameters */ 1,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[885],
+    /* parameters */ &kParameters[881],
     /* return matcher indices */ &kMatcherIndices[101],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::pack4x8unorm,
   },
   {
-    /* [448] */
+    /* [446] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13706,7 +13657,7 @@
     /* const eval */ &ConstEval::reflect,
   },
   {
-    /* [449] */
+    /* [447] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13718,115 +13669,115 @@
     /* const eval */ &ConstEval::refract,
   },
   {
-    /* [450] */
+    /* [448] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ nullptr,
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
-    /* [451] */
+    /* [449] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 2,
     /* template types */ &kTemplateTypes[23],
     /* template numbers */ &kTemplateNumbers[3],
-    /* parameters */ &kParameters[908],
+    /* parameters */ &kParameters[904],
     /* return matcher indices */ &kMatcherIndices[18],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::transpose,
   },
   {
+    /* [450] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[907],
+    /* return matcher indices */ &kMatcherIndices[108],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::unpack2x16float,
+  },
+  {
+    /* [451] */
+    /* num parameters */ 1,
+    /* num template types */ 0,
+    /* num template numbers */ 0,
+    /* template types */ &kTemplateTypes[37],
+    /* template numbers */ &kTemplateNumbers[10],
+    /* parameters */ &kParameters[908],
+    /* return matcher indices */ &kMatcherIndices[108],
+    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
+    /* const eval */ &ConstEval::unpack2x16snorm,
+  },
+  {
     /* [452] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[911],
+    /* parameters */ &kParameters[909],
     /* return matcher indices */ &kMatcherIndices[108],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::unpack2x16float,
+    /* const eval */ &ConstEval::unpack2x16unorm,
   },
   {
     /* [453] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[912],
-    /* return matcher indices */ &kMatcherIndices[108],
+    /* parameters */ &kParameters[910],
+    /* return matcher indices */ &kMatcherIndices[110],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::unpack2x16snorm,
+    /* const eval */ &ConstEval::unpack4x8snorm,
   },
   {
     /* [454] */
     /* num parameters */ 1,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[913],
-    /* return matcher indices */ &kMatcherIndices[108],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::unpack2x16unorm,
-  },
-  {
-    /* [455] */
-    /* num parameters */ 1,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[914],
-    /* return matcher indices */ &kMatcherIndices[110],
-    /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
-    /* const eval */ &ConstEval::unpack4x8snorm,
-  },
-  {
-    /* [456] */
-    /* num parameters */ 1,
-    /* num template types */ 0,
-    /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
-    /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[915],
+    /* parameters */ &kParameters[911],
     /* return matcher indices */ &kMatcherIndices[110],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::unpack4x8unorm,
   },
   {
-    /* [457] */
+    /* [455] */
     /* num parameters */ 0,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[1016],
+    /* parameters */ &kParameters[1012],
     /* return matcher indices */ nullptr,
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
-    /* [458] */
+    /* [456] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 1,
     /* template types */ &kTemplateTypes[25],
     /* template numbers */ &kTemplateNumbers[9],
-    /* parameters */ &kParameters[950],
+    /* parameters */ &kParameters[946],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ nullptr,
   },
   {
-    /* [459] */
+    /* [457] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13838,7 +13789,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [460] */
+    /* [458] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13850,7 +13801,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [461] */
+    /* [459] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13862,7 +13813,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [462] */
+    /* [460] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13874,7 +13825,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [463] */
+    /* [461] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13886,7 +13837,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [464] */
+    /* [462] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13898,7 +13849,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [465] */
+    /* [463] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13910,7 +13861,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [466] */
+    /* [464] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13922,7 +13873,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [467] */
+    /* [465] */
     /* num parameters */ 2,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13934,7 +13885,7 @@
     /* const eval */ nullptr,
   },
   {
-    /* [468] */
+    /* [466] */
     /* num parameters */ 3,
     /* num template types */ 1,
     /* num template numbers */ 1,
@@ -13946,23 +13897,23 @@
     /* const eval */ nullptr,
   },
   {
-    /* [469] */
+    /* [467] */
     /* num parameters */ 1,
     /* num template types */ 1,
     /* num template numbers */ 0,
     /* template types */ &kTemplateTypes[24],
     /* template numbers */ &kTemplateNumbers[10],
-    /* parameters */ &kParameters[951],
+    /* parameters */ &kParameters[947],
     /* return matcher indices */ &kMatcherIndices[3],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
     /* const eval */ &ConstEval::Identity,
   },
   {
-    /* [470] */
+    /* [468] */
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[746],
     /* return matcher indices */ &kMatcherIndices[35],
@@ -13970,11 +13921,11 @@
     /* const eval */ nullptr,
   },
   {
-    /* [471] */
+    /* [469] */
     /* num parameters */ 2,
     /* num template types */ 0,
     /* num template numbers */ 0,
-    /* template types */ &kTemplateTypes[38],
+    /* template types */ &kTemplateTypes[37],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[748],
     /* return matcher indices */ &kMatcherIndices[35],
@@ -13989,357 +13940,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[287],
+    /* 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[289],
+    /* overloads */ &kOverloads[285],
   },
   {
     /* [2] */
     /* fn acosh<T : fa_f32_f16>(@test_value(2) T) -> T */
     /* fn acosh<N : num, T : fa_f32_f16>(@test_value(2) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[291],
+    /* overloads */ &kOverloads[287],
   },
   {
     /* [3] */
     /* fn all(bool) -> bool */
     /* fn all<N : num>(vec<N, bool>) -> bool */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[293],
+    /* overloads */ &kOverloads[289],
   },
   {
     /* [4] */
     /* fn any(bool) -> bool */
     /* fn any<N : num>(vec<N, bool>) -> bool */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[295],
+    /* overloads */ &kOverloads[291],
   },
   {
     /* [5] */
     /* fn arrayLength<T, A : access>(ptr<storage, array<T>, A>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[435],
+    /* 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[297],
+    /* 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[299],
+    /* 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[301],
+    /* 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[303],
+    /* 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[305],
+    /* 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[307],
+    /* 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[309],
+    /* 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[311],
+    /* 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[313],
+    /* 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[315],
+    /* 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[317],
+    /* 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[319],
+    /* overloads */ &kOverloads[315],
   },
   {
     /* [18] */
     /* fn cross<T : fa_f32_f16>(vec3<T>, vec3<T>) -> vec3<T> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[436],
+    /* 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[321],
+    /* overloads */ &kOverloads[317],
   },
   {
     /* [20] */
     /* fn determinant<N : num, T : fa_f32_f16>(mat<N, N, T>) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[437],
+    /* 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[323],
+    /* overloads */ &kOverloads[319],
   },
   {
     /* [22] */
     /* fn dot<N : num, T : fia_fiu32_f16>(vec<N, T>, vec<N, T>) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[438],
+    /* overloads */ &kOverloads[436],
   },
   {
     /* [23] */
     /* fn dot4I8Packed(u32, u32) -> i32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[439],
+    /* overloads */ &kOverloads[437],
   },
   {
     /* [24] */
     /* fn dot4U8Packed(u32, u32) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[440],
+    /* overloads */ &kOverloads[438],
   },
   {
     /* [25] */
     /* fn dpdx(f32) -> f32 */
     /* fn dpdx<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[325],
+    /* overloads */ &kOverloads[321],
   },
   {
     /* [26] */
     /* fn dpdxCoarse(f32) -> f32 */
     /* fn dpdxCoarse<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[327],
+    /* overloads */ &kOverloads[323],
   },
   {
     /* [27] */
     /* fn dpdxFine(f32) -> f32 */
     /* fn dpdxFine<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[329],
+    /* overloads */ &kOverloads[325],
   },
   {
     /* [28] */
     /* fn dpdy(f32) -> f32 */
     /* fn dpdy<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[331],
+    /* overloads */ &kOverloads[327],
   },
   {
     /* [29] */
     /* fn dpdyCoarse(f32) -> f32 */
     /* fn dpdyCoarse<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[333],
+    /* overloads */ &kOverloads[329],
   },
   {
     /* [30] */
     /* fn dpdyFine(f32) -> f32 */
     /* fn dpdyFine<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[335],
+    /* 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[337],
+    /* 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[339],
+    /* 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[341],
+    /* 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[441],
+    /* 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[343],
+    /* 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[345],
+    /* 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[347],
+    /* 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[349],
+    /* overloads */ &kOverloads[345],
   },
   {
     /* [39] */
     /* fn fract<T : f32_f16>(T) -> T */
     /* fn fract<N : num, T : f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[351],
+    /* 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[353],
+    /* overloads */ &kOverloads[349],
   },
   {
     /* [41] */
     /* fn fwidth(f32) -> f32 */
     /* fn fwidth<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[355],
+    /* overloads */ &kOverloads[351],
   },
   {
     /* [42] */
     /* fn fwidthCoarse(f32) -> f32 */
     /* fn fwidthCoarse<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[357],
+    /* overloads */ &kOverloads[353],
   },
   {
     /* [43] */
     /* fn fwidthFine(f32) -> f32 */
     /* fn fwidthFine<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[359],
+    /* 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[361],
+    /* 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[363],
+    /* overloads */ &kOverloads[359],
   },
   {
     /* [46] */
     /* fn ldexp<T : f32_f16>(T, i32) -> T */
     /* fn ldexp<N : num, T : f32_f16>(vec<N, T>, vec<N, i32>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[365],
+    /* 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[367],
+    /* 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[369],
+    /* 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[371],
+    /* 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[373],
+    /* 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[375],
+    /* overloads */ &kOverloads[371],
   },
   {
     /* [52] */
@@ -14347,104 +14298,104 @@
     /* fn mix<N : num, T : f32_f16>(vec<N, T>, vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* fn mix<N : num, T : f32_f16>(vec<N, T>, vec<N, T>, T) -> vec<N, T> */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[266],
+    /* 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[377],
+    /* overloads */ &kOverloads[373],
   },
   {
     /* [54] */
     /* fn normalize<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[442],
+    /* overloads */ &kOverloads[440],
   },
   {
     /* [55] */
     /* fn pack2x16float(vec2<f32>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[443],
+    /* overloads */ &kOverloads[441],
   },
   {
     /* [56] */
     /* fn pack2x16snorm(vec2<f32>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[444],
+    /* overloads */ &kOverloads[442],
   },
   {
     /* [57] */
     /* fn pack2x16unorm(vec2<f32>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[445],
+    /* overloads */ &kOverloads[443],
   },
   {
     /* [58] */
     /* fn pack4x8snorm(vec4<f32>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[446],
+    /* overloads */ &kOverloads[444],
   },
   {
     /* [59] */
     /* fn pack4x8unorm(vec4<f32>) -> u32 */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[447],
+    /* overloads */ &kOverloads[445],
   },
   {
     /* [60] */
     /* fn pow<T : f32_f16>(T, T) -> T */
     /* fn pow<N : num, T : f32_f16>(vec<N, T>, vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[379],
+    /* overloads */ &kOverloads[375],
   },
   {
     /* [61] */
     /* fn quantizeToF16(f32) -> f32 */
     /* fn quantizeToF16<N : num>(vec<N, f32>) -> vec<N, f32> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[381],
+    /* 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[383],
+    /* 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[448],
+    /* 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[449],
+    /* 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[385],
+    /* overloads */ &kOverloads[381],
   },
   {
     /* [66] */
     /* fn round<T : fa_f32_f16>(@test_value(3.4) T) -> T */
     /* fn round<N : num, T : fa_f32_f16>(@test_value(3.4) vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[387],
+    /* 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[389],
+    /* overloads */ &kOverloads[385],
   },
   {
     /* [68] */
@@ -14452,118 +14403,118 @@
     /* 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[269],
+    /* overloads */ &kOverloads[265],
   },
   {
     /* [69] */
     /* fn sign<T : fa_f32_f16>(T) -> T */
     /* fn sign<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[391],
+    /* overloads */ &kOverloads[387],
   },
   {
     /* [70] */
     /* fn sin<T : fa_f32_f16>(T) -> T */
     /* fn sin<N : num, T : fa_f32_f16>(vec<N, T>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[393],
+    /* 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[395],
+    /* 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[397],
+    /* 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[399],
+    /* 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[401],
+    /* overloads */ &kOverloads[397],
   },
   {
     /* [75] */
     /* fn storageBarrier() */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[450],
+    /* 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[403],
+    /* 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[405],
+    /* 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[451],
+    /* 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[407],
+    /* overloads */ &kOverloads[403],
   },
   {
     /* [80] */
     /* fn unpack2x16float(u32) -> vec2<f32> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[452],
+    /* overloads */ &kOverloads[450],
   },
   {
     /* [81] */
     /* fn unpack2x16snorm(u32) -> vec2<f32> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[453],
+    /* overloads */ &kOverloads[451],
   },
   {
     /* [82] */
     /* fn unpack2x16unorm(u32) -> vec2<f32> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[454],
+    /* overloads */ &kOverloads[452],
   },
   {
     /* [83] */
     /* fn unpack4x8snorm(u32) -> vec4<f32> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[455],
+    /* overloads */ &kOverloads[453],
   },
   {
     /* [84] */
     /* fn unpack4x8unorm(u32) -> vec4<f32> */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[456],
+    /* overloads */ &kOverloads[454],
   },
   {
     /* [85] */
     /* fn workgroupBarrier() */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[457],
+    /* overloads */ &kOverloads[455],
   },
   {
     /* [86] */
@@ -14655,7 +14606,7 @@
     /* fn textureNumSamples<T : fiu32>(texture: texture_multisampled_2d<T>) -> u32 */
     /* fn textureNumSamples(texture: texture_depth_multisampled_2d) -> u32 */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[409],
+    /* overloads */ &kOverloads[405],
   },
   {
     /* [92] */
@@ -14749,7 +14700,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[411],
+    /* overloads */ &kOverloads[407],
   },
   {
     /* [99] */
@@ -14786,73 +14737,73 @@
     /* [101] */
     /* fn atomicLoad<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[458],
+    /* overloads */ &kOverloads[456],
   },
   {
     /* [102] */
     /* fn atomicStore<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[459],
+    /* overloads */ &kOverloads[457],
   },
   {
     /* [103] */
     /* fn atomicAdd<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[460],
+    /* overloads */ &kOverloads[458],
   },
   {
     /* [104] */
     /* fn atomicSub<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[461],
+    /* overloads */ &kOverloads[459],
   },
   {
     /* [105] */
     /* fn atomicMax<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[462],
+    /* overloads */ &kOverloads[460],
   },
   {
     /* [106] */
     /* fn atomicMin<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[463],
+    /* overloads */ &kOverloads[461],
   },
   {
     /* [107] */
     /* fn atomicAnd<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[464],
+    /* overloads */ &kOverloads[462],
   },
   {
     /* [108] */
     /* fn atomicOr<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[465],
+    /* overloads */ &kOverloads[463],
   },
   {
     /* [109] */
     /* fn atomicXor<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[466],
+    /* overloads */ &kOverloads[464],
   },
   {
     /* [110] */
     /* fn atomicExchange<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[467],
+    /* overloads */ &kOverloads[465],
   },
   {
     /* [111] */
     /* 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[468],
+    /* overloads */ &kOverloads[466],
   },
   {
     /* [112] */
     /* fn _tint_materialize<T>(T) -> T */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[469],
+    /* overloads */ &kOverloads[467],
   },
 };
 
@@ -14862,21 +14813,21 @@
     /* op !(bool) -> bool */
     /* op !<N : num>(vec<N, bool>) -> vec<N, bool> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[413],
+    /* 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[415],
+    /* 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[417],
+    /* overloads */ &kOverloads[413],
   },
 };
 constexpr uint8_t kUnaryOperatorNot = 0;
@@ -14929,10 +14880,10 @@
   },
   {
     /* [4] */
-    /* op %<T : fiu32_f16>(T, T) -> T */
-    /* op %<T : fiu32_f16, N : num>(vec<N, T>, vec<N, T>) -> vec<N, T> */
-    /* op %<T : fiu32_f16, N : num>(vec<N, T>, T) -> vec<N, T> */
-    /* op %<T : fiu32_f16, N : num>(T, vec<N, T>) -> vec<N, T> */
+    /* op %<T : fia_fiu32_f16>(T, T) -> T */
+    /* op %<T : fia_fiu32_f16, N : num>(vec<N, T>, vec<N, T>) -> vec<N, T> */
+    /* 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[250],
   },
@@ -14941,7 +14892,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 */ 2,
-    /* overloads */ &kOverloads[419],
+    /* overloads */ &kOverloads[415],
   },
   {
     /* [6] */
@@ -14965,71 +14916,69 @@
     /* [8] */
     /* op &&(bool, bool) -> bool */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[470],
+    /* overloads */ &kOverloads[468],
   },
   {
     /* [9] */
     /* op ||(bool, bool) -> bool */
     /* num overloads */ 1,
-    /* overloads */ &kOverloads[471],
+    /* overloads */ &kOverloads[469],
   },
   {
     /* [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[421],
+    /* 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[423],
+    /* 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[425],
+    /* 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[427],
+    /* 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[429],
+    /* 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[431],
+    /* overloads */ &kOverloads[427],
   },
   {
     /* [16] */
-    /* op <<<T : iu32>(T, u32) -> T */
-    /* op <<<T : iu32, N : num>(vec<N, T>, vec<N, u32>) -> vec<N, T> */
-    /* op <<<T : ia>(T, u32) -> T */
-    /* op <<<T : ia, N : num>(vec<N, T>, vec<N, u32>) -> vec<N, T> */
-    /* num overloads */ 4,
-    /* overloads */ &kOverloads[262],
+    /* 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],
   },
   {
     /* [17] */
     /* op >><T : iu32>(T, u32) -> T */
     /* op >><T : iu32, N : num>(vec<N, T>, vec<N, u32>) -> vec<N, T> */
     /* num overloads */ 2,
-    /* overloads */ &kOverloads[433],
+    /* overloads */ &kOverloads[431],
   },
 };
 constexpr uint8_t kBinaryOperatorPlus = 0;
@@ -15058,7 +15007,7 @@
     /* init i32(i32) -> i32 */
     /* conv i32<T : scalar_no_i32>(T) -> i32 */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[272],
+    /* overloads */ &kOverloads[268],
   },
   {
     /* [1] */
@@ -15066,7 +15015,7 @@
     /* init u32(u32) -> u32 */
     /* conv u32<T : scalar_no_u32>(T) -> u32 */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[275],
+    /* overloads */ &kOverloads[271],
   },
   {
     /* [2] */
@@ -15074,7 +15023,7 @@
     /* init f32(f32) -> f32 */
     /* conv f32<T : scalar_no_f32>(T) -> f32 */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[278],
+    /* overloads */ &kOverloads[274],
   },
   {
     /* [3] */
@@ -15082,7 +15031,7 @@
     /* init f16(f16) -> f16 */
     /* conv f16<T : scalar_no_f16>(T) -> f16 */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[281],
+    /* overloads */ &kOverloads[277],
   },
   {
     /* [4] */
@@ -15090,7 +15039,7 @@
     /* init bool(bool) -> bool */
     /* conv bool<T : scalar_no_bool>(T) -> bool */
     /* num overloads */ 3,
-    /* overloads */ &kOverloads[284],
+    /* overloads */ &kOverloads[280],
   },
   {
     /* [5] */
diff --git a/src/tint/resolver/intrinsic_table_test.cc b/src/tint/resolver/intrinsic_table_test.cc
index 1608aa2..03255df 100644
--- a/src/tint/resolver/intrinsic_table_test.cc
+++ b/src/tint/resolver/intrinsic_table_test.cc
@@ -252,7 +252,8 @@
 }
 
 TEST_F(IntrinsicTableTest, MatchArray) {
-    auto* arr = create<sem::Array>(create<sem::U32>(), sem::RuntimeArrayCount{}, 4u, 4u, 4u, 4u);
+    auto* arr =
+        create<sem::Array>(create<sem::U32>(), create<sem::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     auto* arr_ptr = create<sem::Pointer>(arr, ast::AddressSpace::kStorage, ast::Access::kReadWrite);
     auto result = table->Lookup(BuiltinType::kArrayLength, utils::Vector{arr_ptr},
                                 sem::EvaluationStage::kConstant, Source{});
@@ -955,7 +956,8 @@
 }
 
 TEST_F(IntrinsicTableTest, MismatchTypeConversion) {
-    auto* arr = create<sem::Array>(create<sem::U32>(), sem::RuntimeArrayCount{}, 4u, 4u, 4u, 4u);
+    auto* arr =
+        create<sem::Array>(create<sem::U32>(), create<sem::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     auto* f32 = create<sem::F32>();
     auto result = table->Lookup(InitConvIntrinsic::kVec3, f32, utils::Vector{arr},
                                 sem::EvaluationStage::kConstant, Source{{12, 34}});
diff --git a/src/tint/resolver/is_host_shareable_test.cc b/src/tint/resolver/is_host_shareable_test.cc
index ab969fa..5e1555b 100644
--- a/src/tint/resolver/is_host_shareable_test.cc
+++ b/src/tint/resolver/is_host_shareable_test.cc
@@ -106,13 +106,14 @@
 }
 
 TEST_F(ResolverIsHostShareable, ArraySizedOfHostShareable) {
-    auto* arr =
-        create<sem::Array>(create<sem::I32>(), sem::ConstantArrayCount{5u}, 4u, 20u, 4u, 4u);
+    auto* arr = create<sem::Array>(create<sem::I32>(), create<sem::ConstantArrayCount>(5u), 4u, 20u,
+                                   4u, 4u);
     EXPECT_TRUE(r()->IsHostShareable(arr));
 }
 
 TEST_F(ResolverIsHostShareable, ArrayUnsizedOfHostShareable) {
-    auto* arr = create<sem::Array>(create<sem::I32>(), sem::RuntimeArrayCount{}, 4u, 4u, 4u, 4u);
+    auto* arr =
+        create<sem::Array>(create<sem::I32>(), create<sem::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     EXPECT_TRUE(r()->IsHostShareable(arr));
 }
 
diff --git a/src/tint/resolver/is_storeable_test.cc b/src/tint/resolver/is_storeable_test.cc
index 61cf33b..6618199 100644
--- a/src/tint/resolver/is_storeable_test.cc
+++ b/src/tint/resolver/is_storeable_test.cc
@@ -89,13 +89,14 @@
 }
 
 TEST_F(ResolverIsStorableTest, ArraySizedOfStorable) {
-    auto* arr =
-        create<sem::Array>(create<sem::I32>(), sem::ConstantArrayCount{5u}, 4u, 20u, 4u, 4u);
+    auto* arr = create<sem::Array>(create<sem::I32>(), create<sem::ConstantArrayCount>(5u), 4u, 20u,
+                                   4u, 4u);
     EXPECT_TRUE(r()->IsStorable(arr));
 }
 
 TEST_F(ResolverIsStorableTest, ArrayUnsizedOfStorable) {
-    auto* arr = create<sem::Array>(create<sem::I32>(), sem::RuntimeArrayCount{}, 4u, 4u, 4u, 4u);
+    auto* arr =
+        create<sem::Array>(create<sem::I32>(), create<sem::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     EXPECT_TRUE(r()->IsStorable(arr));
 }
 
diff --git a/src/tint/resolver/override_test.cc b/src/tint/resolver/override_test.cc
index 132bd55..a97c92d 100644
--- a/src/tint/resolver/override_test.cc
+++ b/src/tint/resolver/override_test.cc
@@ -142,7 +142,9 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     {
-        auto& refs = Sem().Get(b)->TransitivelyReferencedOverrides();
+        auto* r = Sem().TransitivelyReferencedOverrides(Sem().Get(b));
+        ASSERT_NE(r, nullptr);
+        auto& refs = *r;
         ASSERT_EQ(refs.Length(), 1u);
         EXPECT_EQ(refs[0], Sem().Get(a));
     }
@@ -167,7 +169,9 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     {
-        auto& refs = Sem().Get<sem::GlobalVariable>(b)->TransitivelyReferencedOverrides();
+        auto* r = Sem().TransitivelyReferencedOverrides(Sem().Get<sem::GlobalVariable>(b));
+        ASSERT_NE(r, nullptr);
+        auto& refs = *r;
         ASSERT_EQ(refs.Length(), 1u);
         EXPECT_EQ(refs[0], Sem().Get(a));
     }
@@ -215,14 +219,18 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     {
-        auto& refs = Sem().Get(arr_ty)->TransitivelyReferencedOverrides();
+        auto* r = Sem().TransitivelyReferencedOverrides(Sem().Get(arr_ty));
+        ASSERT_NE(r, nullptr);
+        auto& refs = *r;
         ASSERT_EQ(refs.Length(), 2u);
         EXPECT_EQ(refs[0], Sem().Get(b));
         EXPECT_EQ(refs[1], Sem().Get(a));
     }
 
     {
-        auto& refs = Sem().Get<sem::GlobalVariable>(arr)->TransitivelyReferencedOverrides();
+        auto* r = Sem().TransitivelyReferencedOverrides(Sem().Get<sem::GlobalVariable>(arr));
+        ASSERT_NE(r, nullptr);
+        auto& refs = *r;
         ASSERT_EQ(refs.Length(), 2u);
         EXPECT_EQ(refs[0], Sem().Get(b));
         EXPECT_EQ(refs[1], Sem().Get(a));
@@ -251,14 +259,18 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     {
-        auto& refs = Sem().Get<sem::Array>(arr_ty->type)->TransitivelyReferencedOverrides();
+        auto* r = Sem().TransitivelyReferencedOverrides(Sem().Get<sem::Array>(arr_ty->type));
+        ASSERT_NE(r, nullptr);
+        auto& refs = *r;
         ASSERT_EQ(refs.Length(), 2u);
         EXPECT_EQ(refs[0], Sem().Get(b));
         EXPECT_EQ(refs[1], Sem().Get(a));
     }
 
     {
-        auto& refs = Sem().Get<sem::GlobalVariable>(arr)->TransitivelyReferencedOverrides();
+        auto* r = Sem().TransitivelyReferencedOverrides(Sem().Get<sem::GlobalVariable>(arr));
+        ASSERT_NE(r, nullptr);
+        auto& refs = *r;
         ASSERT_EQ(refs.Length(), 2u);
         EXPECT_EQ(refs[0], Sem().Get(b));
         EXPECT_EQ(refs[1], Sem().Get(a));
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index cf55a1d..ca66ac2 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -51,6 +51,7 @@
 #include "src/tint/ast/vector.h"
 #include "src/tint/ast/while_statement.h"
 #include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/resolver/type_alias.h"
 #include "src/tint/resolver/uniformity.h"
 #include "src/tint/sem/abstract_float.h"
 #include "src/tint/sem/abstract_int.h"
@@ -329,13 +330,16 @@
                     AddNote("'" + name + "' declared here", func->Declaration()->source);
                     return nullptr;
                 },
-                [&](Default) {
+                [&](Default) -> sem::Type* {
                     if (auto* tn = ty->As<ast::TypeName>()) {
                         if (IsBuiltin(tn->name)) {
                             auto name = builder_->Symbols().NameFor(tn->name);
                             AddError("cannot use builtin '" + name + "' as type", ty->source);
                             return nullptr;
                         }
+                        if (auto* t = BuiltinTypeAlias(tn->name)) {
+                            return t;
+                        }
                     }
                     TINT_UNREACHABLE(Resolver, diagnostics_)
                         << "Unhandled resolved type '"
@@ -913,11 +917,14 @@
 
     // Track the pipeline-overridable constants that are transitively referenced by this variable.
     for (auto* var : transitively_referenced_overrides) {
-        sem->AddTransitivelyReferencedOverride(var);
+        builder_->Sem().AddTransitivelyReferencedOverride(sem, var);
     }
     if (auto* arr = sem->Type()->UnwrapRef()->As<sem::Array>()) {
-        for (auto* var : arr->TransitivelyReferencedOverrides()) {
-            sem->AddTransitivelyReferencedOverride(var);
+        auto* refs = builder_->Sem().TransitivelyReferencedOverrides(arr);
+        if (refs) {
+            for (auto* var : *refs) {
+                builder_->Sem().AddTransitivelyReferencedOverride(sem, var);
+            }
         }
     }
 
@@ -2136,8 +2143,7 @@
             [&](const ast::Array* a) -> sem::Call* {
                 Mark(a);
                 // array element type must be inferred if it was not specified.
-                sem::ArrayCount el_count =
-                    sem::ConstantArrayCount{static_cast<uint32_t>(args.Length())};
+                const sem::ArrayCount* el_count = nullptr;
                 const sem::Type* el_ty = nullptr;
                 if (a->type) {
                     el_ty = Type(a->type);
@@ -2148,14 +2154,15 @@
                         AddError("cannot construct a runtime-sized array", expr->source);
                         return nullptr;
                     }
-                    if (auto count = ArrayCount(a->count)) {
-                        el_count = count.Get();
-                    } else {
+                    el_count = ArrayCount(a->count);
+                    if (!el_count) {
                         return nullptr;
                     }
                     // Note: validation later will detect any mismatches between explicit array
                     // size and number of initializer expressions.
                 } else {
+                    el_count = builder_->create<sem::ConstantArrayCount>(
+                        static_cast<uint32_t>(args.Length()));
                     auto arg_tys =
                         utils::Transform(args, [](auto* arg) { return arg->Type()->UnwrapRef(); });
                     el_ty = sem::Type::Common(arg_tys);
@@ -2228,11 +2235,13 @@
             },
             [&](Default) -> sem::Call* {
                 auto name = builder_->Symbols().NameFor(ident->symbol);
-                auto builtin_type = sem::ParseBuiltinType(name);
-                if (builtin_type != sem::BuiltinType::kNone) {
+                if (auto* alias = BuiltinTypeAlias(ident->symbol)) {
+                    return ty_init_or_conv(alias);
+                }
+                if (auto builtin_type = sem::ParseBuiltinType(name);
+                    builtin_type != sem::BuiltinType::kNone) {
                     return BuiltinCall(expr, builtin_type, args);
                 }
-
                 TINT_ICE(Resolver, diagnostics_)
                     << expr->source << " unhandled CallExpression target:\n"
                     << "resolved: " << (resolved ? resolved->TypeInfo().name : "<null>") << "\n"
@@ -2328,6 +2337,40 @@
     return call;
 }
 
+sem::Type* Resolver::BuiltinTypeAlias(Symbol sym) const {
+    auto name = builder_->Symbols().NameFor(sym);
+    auto& b = *builder_;
+    switch (ParseTypeAlias(name)) {
+        case TypeAlias::kVec2F:
+            return b.create<sem::Vector>(b.create<sem::F32>(), 2u);
+        case TypeAlias::kVec3F:
+            return b.create<sem::Vector>(b.create<sem::F32>(), 3u);
+        case TypeAlias::kVec4F:
+            return b.create<sem::Vector>(b.create<sem::F32>(), 4u);
+        case TypeAlias::kVec2H:
+            return b.create<sem::Vector>(b.create<sem::F16>(), 2u);
+        case TypeAlias::kVec3H:
+            return b.create<sem::Vector>(b.create<sem::F16>(), 3u);
+        case TypeAlias::kVec4H:
+            return b.create<sem::Vector>(b.create<sem::F16>(), 4u);
+        case TypeAlias::kVec2I:
+            return b.create<sem::Vector>(b.create<sem::I32>(), 2u);
+        case TypeAlias::kVec3I:
+            return b.create<sem::Vector>(b.create<sem::I32>(), 3u);
+        case TypeAlias::kVec4I:
+            return b.create<sem::Vector>(b.create<sem::I32>(), 4u);
+        case TypeAlias::kVec2U:
+            return b.create<sem::Vector>(b.create<sem::U32>(), 2u);
+        case TypeAlias::kVec3U:
+            return b.create<sem::Vector>(b.create<sem::U32>(), 3u);
+        case TypeAlias::kVec4U:
+            return b.create<sem::Vector>(b.create<sem::U32>(), 4u);
+        case TypeAlias::kUndefined:
+            break;
+    }
+    return nullptr;
+}
+
 void Resolver::CollectTextureSamplerPairs(const sem::Builtin* builtin,
                                           utils::VectorRef<const sem::Expression*> args) const {
     // Collect a texture/sampler pair for this builtin.
@@ -2513,8 +2556,11 @@
         if (current_function_) {
             if (global) {
                 current_function_->AddDirectlyReferencedGlobal(global);
-                for (auto* var : global->TransitivelyReferencedOverrides()) {
-                    current_function_->AddTransitivelyReferencedGlobal(var);
+                auto* refs = builder_->Sem().TransitivelyReferencedOverrides(global);
+                if (refs) {
+                    for (auto* var : *refs) {
+                        current_function_->AddTransitivelyReferencedGlobal(var);
+                    }
                 }
             }
         } else if (variable->Declaration()->Is<ast::Override>()) {
@@ -2522,8 +2568,11 @@
                 // Track the reference to this pipeline-overridable constant and any other
                 // pipeline-overridable constants that it references.
                 resolved_overrides_->Add(global);
-                for (auto* var : global->TransitivelyReferencedOverrides()) {
-                    resolved_overrides_->Add(var);
+                auto* refs = builder_->Sem().TransitivelyReferencedOverrides(global);
+                if (refs) {
+                    for (auto* var : *refs) {
+                        resolved_overrides_->Add(var);
+                    }
                 }
             }
         } else if (variable->Declaration()->Is<ast::Var>()) {
@@ -2550,7 +2599,7 @@
         return nullptr;
     }
 
-    if (resolved->Is<sem::Type>()) {
+    if (resolved->Is<sem::Type>() || BuiltinTypeAlias(symbol)) {
         AddError("missing '(' for type initializer or cast", expr->source.End());
         return nullptr;
     }
@@ -2887,15 +2936,16 @@
         return nullptr;
     }
 
-    sem::ArrayCount el_count = sem::RuntimeArrayCount{};
+    const sem::ArrayCount* el_count = nullptr;
 
     // Evaluate the constant array count expression.
     if (auto* count_expr = arr->count) {
-        if (auto count = ArrayCount(count_expr)) {
-            el_count = count.Get();
-        } else {
+        el_count = ArrayCount(count_expr);
+        if (!el_count) {
             return nullptr;
         }
+    } else {
+        el_count = builder_->create<sem::RuntimeArrayCount>();
     }
 
     auto* out = Array(arr->type->source,                              //
@@ -2916,17 +2966,17 @@
     // Track the pipeline-overridable constants that are transitively referenced by this array
     // type.
     for (auto* var : transitively_referenced_overrides) {
-        out->AddTransitivelyReferencedOverride(var);
+        builder_->Sem().AddTransitivelyReferencedOverride(out, var);
     }
 
     return out;
 }
 
-utils::Result<sem::ArrayCount> Resolver::ArrayCount(const ast::Expression* count_expr) {
+const sem::ArrayCount* Resolver::ArrayCount(const ast::Expression* count_expr) {
     // Evaluate the constant array count expression.
     const auto* count_sem = Materialize(Expression(count_expr));
     if (!count_sem) {
-        return utils::Failure;
+        return nullptr;
     }
 
     if (count_sem->Stage() == sem::EvaluationStage::kOverride) {
@@ -2934,34 +2984,34 @@
         // Is the count a named 'override'?
         if (auto* user = count_sem->UnwrapMaterialize()->As<sem::VariableUser>()) {
             if (auto* global = user->Variable()->As<sem::GlobalVariable>()) {
-                return sem::ArrayCount{sem::NamedOverrideArrayCount{global}};
+                return builder_->create<sem::NamedOverrideArrayCount>(global);
             }
         }
-        return sem::ArrayCount{sem::UnnamedOverrideArrayCount{count_sem}};
+        return builder_->create<sem::UnnamedOverrideArrayCount>(count_sem);
     }
 
     auto* count_val = count_sem->ConstantValue();
     if (!count_val) {
         AddError("array count must evaluate to a constant integer expression or override variable",
                  count_expr->source);
-        return utils::Failure;
+        return nullptr;
     }
 
     if (auto* ty = count_val->Type(); !ty->is_integer_scalar()) {
         AddError("array count must evaluate to a constant integer expression, but is type '" +
                      builder_->FriendlyName(ty) + "'",
                  count_expr->source);
-        return utils::Failure;
+        return nullptr;
     }
 
     int64_t count = count_val->As<AInt>();
     if (count < 1) {
         AddError("array count (" + std::to_string(count) + ") must be greater than 0",
                  count_expr->source);
-        return utils::Failure;
+        return nullptr;
     }
 
-    return sem::ArrayCount{sem::ConstantArrayCount{static_cast<uint32_t>(count)}};
+    return builder_->create<sem::ConstantArrayCount>(static_cast<uint32_t>(count));
 }
 
 bool Resolver::ArrayAttributes(utils::VectorRef<const ast::Attribute*> attributes,
@@ -2997,7 +3047,7 @@
 sem::Array* Resolver::Array(const Source& el_source,
                             const Source& count_source,
                             const sem::Type* el_ty,
-                            sem::ArrayCount el_count,
+                            const sem::ArrayCount* el_count,
                             uint32_t explicit_stride) {
     uint32_t el_align = el_ty->Align();
     uint32_t el_size = el_ty->Size();
@@ -3005,7 +3055,7 @@
     uint64_t stride = explicit_stride ? explicit_stride : implicit_stride;
     uint64_t size = 0;
 
-    if (auto const_count = std::get_if<sem::ConstantArrayCount>(&el_count)) {
+    if (auto const_count = el_count->As<sem::ConstantArrayCount>()) {
         size = const_count->value * stride;
         if (size > std::numeric_limits<uint32_t>::max()) {
             std::stringstream msg;
@@ -3014,7 +3064,7 @@
             AddError(msg.str(), count_source);
             return nullptr;
         }
-    } else if (std::holds_alternative<sem::RuntimeArrayCount>(el_count)) {
+    } else if (el_count->Is<sem::RuntimeArrayCount>()) {
         size = stride;
     }
     auto* out = builder_->create<sem::Array>(el_ty, el_count, el_align, static_cast<uint32_t>(size),
@@ -3124,7 +3174,6 @@
                         AddError("offsets must be in ascending order", o->source);
                         return false;
                     }
-                    align = 1;
                     has_offset_attr = true;
                     return true;
                 },
@@ -3225,7 +3274,7 @@
         }
 
         auto* sem_member = builder_->create<sem::StructMember>(
-            member, member->symbol, type, static_cast<uint32_t>(sem_members.size()),
+            member, member->source, member->symbol, type, static_cast<uint32_t>(sem_members.size()),
             static_cast<uint32_t>(offset), static_cast<uint32_t>(align),
             static_cast<uint32_t>(size), location);
         builder_->Sem().Add(member, sem_member);
@@ -3250,13 +3299,13 @@
     }
 
     auto* out = builder_->create<sem::Struct>(
-        str, str->name, sem_members, static_cast<uint32_t>(struct_align),
+        str, str->source, str->name, sem_members, static_cast<uint32_t>(struct_align),
         static_cast<uint32_t>(struct_size), static_cast<uint32_t>(size_no_padding));
 
     for (size_t i = 0; i < sem_members.size(); i++) {
         auto* mem_type = sem_members[i]->Type();
         if (mem_type->Is<sem::Atomic>()) {
-            atomic_composite_info_.Add(out, &sem_members[i]->Declaration()->source);
+            atomic_composite_info_.Add(out, &sem_members[i]->Source());
             break;
         } else {
             if (auto found = atomic_composite_info_.Get(mem_type)) {
@@ -3580,8 +3629,8 @@
                                               decl->type->source)) {
                 std::stringstream err;
                 err << "while analyzing structure member " << sem_.TypeNameOf(str) << "."
-                    << builder_->Symbols().NameFor(decl->symbol);
-                AddNote(err.str(), decl->source);
+                    << builder_->Symbols().NameFor(member->Name());
+                AddNote(err.str(), member->Source());
                 return false;
             }
         }
@@ -3590,7 +3639,7 @@
 
     if (auto* arr = ty->As<sem::Array>()) {
         if (address_space != ast::AddressSpace::kStorage) {
-            if (arr->IsRuntimeSized()) {
+            if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
                 AddError("runtime-sized arrays can only be used in the <storage> address space",
                          usage);
                 return false;
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index abc7633..0deef5f 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -273,7 +273,7 @@
     /// Resolves and validates the expression used as the count parameter of an array.
     /// @param count_expr the expression used as the second template parameter to an array<>.
     /// @returns the number of elements in the array.
-    utils::Result<sem::ArrayCount> ArrayCount(const ast::Expression* count_expr);
+    const sem::ArrayCount* ArrayCount(const ast::Expression* count_expr);
 
     /// Resolves and validates the attributes on an array.
     /// @param attributes the attributes on the array type.
@@ -296,7 +296,7 @@
     sem::Array* Array(const Source& el_source,
                       const Source& count_source,
                       const sem::Type* el_ty,
-                      sem::ArrayCount el_count,
+                      const sem::ArrayCount* el_count,
                       uint32_t explicit_stride);
 
     /// Builds and returns the semantic information for the alias `alias`.
@@ -384,6 +384,7 @@
     /// Set the shadowing information on variable declarations.
     /// @note this method must only be called after all semantic nodes are built.
     void SetShadows();
+
     /// StatementScope() does the following:
     /// * Creates the AST -> SEM mapping.
     /// * Assigns `sem` to #current_statement_
@@ -415,6 +416,9 @@
     /// @returns true if the symbol is the name of a builtin function.
     bool IsBuiltin(Symbol) const;
 
+    /// @returns the builtin type alias for the given symbol
+    sem::Type* BuiltinTypeAlias(Symbol) const;
+
     // ArrayInitializerSig represents a unique array initializer signature.
     // It is a tuple of the array type, number of arguments provided and earliest evaluation stage.
     using ArrayInitializerSig =
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index 13b1da1..2a88249 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -440,7 +440,7 @@
     auto* ref = TypeOf(a)->As<sem::Reference>();
     ASSERT_NE(ref, nullptr);
     auto* ary = ref->StoreType()->As<sem::Array>();
-    EXPECT_EQ(ary->Count(), sem::ConstantArrayCount{10u});
+    EXPECT_EQ(ary->Count(), create<sem::ConstantArrayCount>(10u));
 }
 
 TEST_F(ResolverTest, ArraySize_SignedLiteral) {
@@ -453,7 +453,7 @@
     auto* ref = TypeOf(a)->As<sem::Reference>();
     ASSERT_NE(ref, nullptr);
     auto* ary = ref->StoreType()->As<sem::Array>();
-    EXPECT_EQ(ary->Count(), sem::ConstantArrayCount{10u});
+    EXPECT_EQ(ary->Count(), create<sem::ConstantArrayCount>(10u));
 }
 
 TEST_F(ResolverTest, ArraySize_UnsignedConst) {
@@ -468,7 +468,7 @@
     auto* ref = TypeOf(a)->As<sem::Reference>();
     ASSERT_NE(ref, nullptr);
     auto* ary = ref->StoreType()->As<sem::Array>();
-    EXPECT_EQ(ary->Count(), sem::ConstantArrayCount{10u});
+    EXPECT_EQ(ary->Count(), create<sem::ConstantArrayCount>(10u));
 }
 
 TEST_F(ResolverTest, ArraySize_SignedConst) {
@@ -483,7 +483,7 @@
     auto* ref = TypeOf(a)->As<sem::Reference>();
     ASSERT_NE(ref, nullptr);
     auto* ary = ref->StoreType()->As<sem::Array>();
-    EXPECT_EQ(ary->Count(), sem::ConstantArrayCount{10u});
+    EXPECT_EQ(ary->Count(), create<sem::ConstantArrayCount>(10u));
 }
 
 TEST_F(ResolverTest, ArraySize_NamedOverride) {
@@ -500,7 +500,7 @@
     auto* ary = ref->StoreType()->As<sem::Array>();
     auto* sem_override = Sem().Get<sem::GlobalVariable>(override);
     ASSERT_NE(sem_override, nullptr);
-    EXPECT_EQ(ary->Count(), sem::NamedOverrideArrayCount{sem_override});
+    EXPECT_EQ(ary->Count(), create<sem::NamedOverrideArrayCount>(sem_override));
 }
 
 TEST_F(ResolverTest, ArraySize_NamedOverride_Equivalence) {
@@ -525,8 +525,8 @@
 
     auto* sem_override = Sem().Get<sem::GlobalVariable>(override);
     ASSERT_NE(sem_override, nullptr);
-    EXPECT_EQ(ary_a->Count(), sem::NamedOverrideArrayCount{sem_override});
-    EXPECT_EQ(ary_b->Count(), sem::NamedOverrideArrayCount{sem_override});
+    EXPECT_EQ(ary_a->Count(), create<sem::NamedOverrideArrayCount>(sem_override));
+    EXPECT_EQ(ary_b->Count(), create<sem::NamedOverrideArrayCount>(sem_override));
     EXPECT_EQ(ary_a, ary_b);
 }
 
@@ -545,7 +545,7 @@
     auto* ary = ref->StoreType()->As<sem::Array>();
     auto* sem_override = Sem().Get<sem::GlobalVariable>(override);
     ASSERT_NE(sem_override, nullptr);
-    EXPECT_EQ(ary->Count(), sem::UnnamedOverrideArrayCount{Sem().Get(cnt)});
+    EXPECT_EQ(ary->Count(), create<sem::UnnamedOverrideArrayCount>(Sem().Get(cnt)));
 }
 
 TEST_F(ResolverTest, ArraySize_UnamedOverride_Equivalence) {
@@ -572,8 +572,8 @@
 
     auto* sem_override = Sem().Get<sem::GlobalVariable>(override);
     ASSERT_NE(sem_override, nullptr);
-    EXPECT_EQ(ary_a->Count(), sem::UnnamedOverrideArrayCount{Sem().Get(a_cnt)});
-    EXPECT_EQ(ary_b->Count(), sem::UnnamedOverrideArrayCount{Sem().Get(b_cnt)});
+    EXPECT_EQ(ary_a->Count(), create<sem::UnnamedOverrideArrayCount>(Sem().Get(a_cnt)));
+    EXPECT_EQ(ary_b->Count(), create<sem::UnnamedOverrideArrayCount>(Sem().Get(b_cnt)));
     EXPECT_NE(ary_a, ary_b);
 }
 
diff --git a/src/tint/resolver/resolver_test_helper.h b/src/tint/resolver/resolver_test_helper.h
index 68a7017..f2567c8 100644
--- a/src/tint/resolver/resolver_test_helper.h
+++ b/src/tint/resolver/resolver_test_helper.h
@@ -31,6 +31,7 @@
 #include "src/tint/sem/expression.h"
 #include "src/tint/sem/statement.h"
 #include "src/tint/sem/variable.h"
+#include "src/tint/traits.h"
 #include "src/tint/utils/vector.h"
 
 namespace tint::resolver {
@@ -658,9 +659,11 @@
     /// @return the semantic array type
     static inline const sem::Type* Sem(ProgramBuilder& b) {
         auto* el = DataType<T>::Sem(b);
-        sem::ArrayCount count = sem::ConstantArrayCount{N};
+        const sem::ArrayCount* count = nullptr;
         if (N == 0) {
-            count = sem::RuntimeArrayCount{};
+            count = b.create<sem::RuntimeArrayCount>();
+        } else {
+            count = b.create<sem::ConstantArrayCount>(N);
         }
         return b.create<sem::Array>(
             /* element */ el,
@@ -793,6 +796,7 @@
 /// Creates a Value of DataType<T> from a scalar `v`
 template <typename T>
 Value Val(T v) {
+    static_assert(traits::IsTypeIn<T, Scalar>, "v must be a Number of bool");
     return Value::Create<T>(utils::Vector<Scalar, 1>{v});
 }
 
diff --git a/src/tint/resolver/struct_layout_test.cc b/src/tint/resolver/struct_layout_test.cc
index 1e5ec45..acfe691 100644
--- a/src/tint/resolver/struct_layout_test.cc
+++ b/src/tint/resolver/struct_layout_test.cc
@@ -555,5 +555,47 @@
     }
 }
 
+TEST_F(ResolverStructLayoutTest, OffsetAttributes) {
+    auto* inner = Structure("Inner", utils::Vector{
+                                         Member("a", ty.f32(), utils::Vector{MemberOffset(8_i)}),
+                                         Member("b", ty.f32(), utils::Vector{MemberOffset(16_i)}),
+                                         Member("c", ty.f32(), utils::Vector{MemberOffset(32_i)}),
+                                     });
+    auto* s = Structure("S", utils::Vector{
+                                 Member("a", ty.f32(), utils::Vector{MemberOffset(4_i)}),
+                                 Member("b", ty.u32(), utils::Vector{MemberOffset(8_i)}),
+                                 Member("c", ty.Of(inner), utils::Vector{MemberOffset(32_i)}),
+                                 Member("d", ty.i32()),
+                                 Member("e", ty.i32(), utils::Vector{MemberOffset(128_i)}),
+                             });
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* sem = TypeOf(s)->As<sem::Struct>();
+    ASSERT_NE(sem, nullptr);
+    EXPECT_EQ(sem->Size(), 132u);
+    EXPECT_EQ(sem->SizeNoPadding(), 132u);
+    EXPECT_EQ(sem->Align(), 4u);
+    ASSERT_EQ(sem->Members().size(), 5u);
+    EXPECT_EQ(sem->Members()[0]->Offset(), 4u);
+    EXPECT_EQ(sem->Members()[0]->Align(), 4u);
+    EXPECT_EQ(sem->Members()[0]->Size(), 4u);
+    EXPECT_EQ(sem->Members()[1]->Offset(), 8u);
+    EXPECT_EQ(sem->Members()[1]->Align(), 4u);
+    EXPECT_EQ(sem->Members()[1]->Size(), 4u);
+    EXPECT_EQ(sem->Members()[2]->Offset(), 32u);
+    EXPECT_EQ(sem->Members()[2]->Align(), 4u);
+    EXPECT_EQ(sem->Members()[2]->Size(), 36u);
+    EXPECT_EQ(sem->Members()[3]->Offset(), 68u);
+    EXPECT_EQ(sem->Members()[3]->Align(), 4u);
+    EXPECT_EQ(sem->Members()[3]->Size(), 4u);
+    EXPECT_EQ(sem->Members()[4]->Offset(), 128u);
+    EXPECT_EQ(sem->Members()[4]->Align(), 4u);
+    EXPECT_EQ(sem->Members()[4]->Size(), 4u);
+    for (auto& m : sem->Members()) {
+        EXPECT_EQ(m->Struct()->Declaration(), s);
+    }
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/type_alias.cc b/src/tint/resolver/type_alias.cc
new file mode 100644
index 0000000..124fc8c
--- /dev/null
+++ b/src/tint/resolver/type_alias.cc
@@ -0,0 +1,102 @@
+// 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/resolver/type_alias.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/resolver/type_alias.h"
+
+namespace tint::resolver {
+
+/// ParseTypeAlias parses a TypeAlias from a string.
+/// @param str the string to parse
+/// @returns the parsed enum, or TypeAlias::kUndefined if the string could not be parsed.
+TypeAlias ParseTypeAlias(std::string_view str) {
+    if (str == "vec2f") {
+        return TypeAlias::kVec2F;
+    }
+    if (str == "vec2h") {
+        return TypeAlias::kVec2H;
+    }
+    if (str == "vec2i") {
+        return TypeAlias::kVec2I;
+    }
+    if (str == "vec2u") {
+        return TypeAlias::kVec2U;
+    }
+    if (str == "vec3f") {
+        return TypeAlias::kVec3F;
+    }
+    if (str == "vec3h") {
+        return TypeAlias::kVec3H;
+    }
+    if (str == "vec3i") {
+        return TypeAlias::kVec3I;
+    }
+    if (str == "vec3u") {
+        return TypeAlias::kVec3U;
+    }
+    if (str == "vec4f") {
+        return TypeAlias::kVec4F;
+    }
+    if (str == "vec4h") {
+        return TypeAlias::kVec4H;
+    }
+    if (str == "vec4i") {
+        return TypeAlias::kVec4I;
+    }
+    if (str == "vec4u") {
+        return TypeAlias::kVec4U;
+    }
+    return TypeAlias::kUndefined;
+}
+
+std::ostream& operator<<(std::ostream& out, TypeAlias value) {
+    switch (value) {
+        case TypeAlias::kUndefined:
+            return out << "undefined";
+        case TypeAlias::kVec2F:
+            return out << "vec2f";
+        case TypeAlias::kVec2H:
+            return out << "vec2h";
+        case TypeAlias::kVec2I:
+            return out << "vec2i";
+        case TypeAlias::kVec2U:
+            return out << "vec2u";
+        case TypeAlias::kVec3F:
+            return out << "vec3f";
+        case TypeAlias::kVec3H:
+            return out << "vec3h";
+        case TypeAlias::kVec3I:
+            return out << "vec3i";
+        case TypeAlias::kVec3U:
+            return out << "vec3u";
+        case TypeAlias::kVec4F:
+            return out << "vec4f";
+        case TypeAlias::kVec4H:
+            return out << "vec4h";
+        case TypeAlias::kVec4I:
+            return out << "vec4i";
+        case TypeAlias::kVec4U:
+            return out << "vec4u";
+    }
+    return out << "<unknown>";
+}
+
+}  // namespace tint::resolver
diff --git a/src/tint/resolver/type_alias.cc.tmpl b/src/tint/resolver/type_alias.cc.tmpl
new file mode 100644
index 0000000..3843864
--- /dev/null
+++ b/src/tint/resolver/type_alias.cc.tmpl
@@ -0,0 +1,25 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate type_alias.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
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "type_alias") -}}
+
+#include "src/tint/resolver/type_alias.h"
+
+namespace tint::resolver {
+
+{{ Eval "ParseEnum" $enum}}
+
+{{ Eval "EnumOStream" $enum}}
+
+}  // namespace tint::resolver
diff --git a/src/tint/resolver/type_alias.h b/src/tint/resolver/type_alias.h
new file mode 100644
index 0000000..40083a3
--- /dev/null
+++ b/src/tint/resolver/type_alias.h
@@ -0,0 +1,64 @@
+// 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/resolver/type_alias.h.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SRC_TINT_RESOLVER_TYPE_ALIAS_H_
+#define SRC_TINT_RESOLVER_TYPE_ALIAS_H_
+
+#include <ostream>
+
+namespace tint::resolver {
+
+/// An enumerator of builtin type aliases.
+enum class TypeAlias {
+    kUndefined,
+    kVec2F,
+    kVec2H,
+    kVec2I,
+    kVec2U,
+    kVec3F,
+    kVec3H,
+    kVec3I,
+    kVec3U,
+    kVec4F,
+    kVec4H,
+    kVec4I,
+    kVec4U,
+};
+
+/// @param out the std::ostream to write to
+/// @param value the TypeAlias
+/// @returns `out` so calls can be chained
+std::ostream& operator<<(std::ostream& out, TypeAlias value);
+
+/// ParseTypeAlias parses a TypeAlias from a string.
+/// @param str the string to parse
+/// @returns the parsed enum, or TypeAlias::kUndefined if the string could not be parsed.
+TypeAlias ParseTypeAlias(std::string_view str);
+
+constexpr const char* kTypeAliasStrings[] = {
+    "vec2f", "vec2h", "vec2i", "vec2u", "vec3f", "vec3h",
+    "vec3i", "vec3u", "vec4f", "vec4h", "vec4i", "vec4u",
+};
+
+}  // namespace tint::resolver
+
+#endif  // SRC_TINT_RESOLVER_TYPE_ALIAS_H_
diff --git a/src/tint/resolver/type_alias.h.tmpl b/src/tint/resolver/type_alias.h.tmpl
new file mode 100644
index 0000000..0a9b2d4
--- /dev/null
+++ b/src/tint/resolver/type_alias.h.tmpl
@@ -0,0 +1,29 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate type_alias.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
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "type_alias") -}}
+
+#ifndef SRC_TINT_RESOLVER_TYPE_ALIAS_H_
+#define SRC_TINT_RESOLVER_TYPE_ALIAS_H_
+
+#include <ostream>
+
+namespace tint::resolver {
+
+/// An enumerator of builtin type aliases.
+{{ Eval "DeclareEnum" $enum}}
+
+}  // namespace tint::resolver
+
+#endif  // SRC_TINT_RESOLVER_TYPE_ALIAS_H_
diff --git a/src/tint/resolver/type_alias_bench.cc b/src/tint/resolver/type_alias_bench.cc
new file mode 100644
index 0000000..2510352
--- /dev/null
+++ b/src/tint/resolver/type_alias_bench.cc
@@ -0,0 +1,57 @@
+// 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/resolver/type_alias_bench.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/resolver/type_alias.h"
+
+#include <array>
+
+#include "benchmark/benchmark.h"
+
+namespace tint::resolver {
+namespace {
+
+void TypeAliasParser(::benchmark::State& state) {
+    std::array kStrings{
+        "veccf",  "32",    "vVc2f",    "vec2f",  "vec21",     "qqeJf",     "vecll7f", "veqH2pp",
+        "vh",     "Gebh",  "vec2h",    "vevi2h", "ve8WWh",    "Mxxc2",     "vgg2i",   "V2X",
+        "vec23",  "vec2i", "vec2E",    "TTeP2i", "vxxcdd",    "v44c2u",    "veVVSSu", "22RRu",
+        "vec2u",  "vF2u",  "vecu",     "ROOHVu", "ecyf",      "n77rrlcGf", "vec340",  "vec3f",
+        "oof",    "vezz",  "1ipp3f",   "XXec3h", "ve9IInn5h", "HHreSSaYh", "vec3h",   "kk3",
+        "jgRR",   "veb",   "vjc3i",    "vc3i",   "vcq",       "vec3i",     "Nec3i",   "vcvv",
+        "ve3QQ",  "vrcf",  "vecju",    "NNew23", "vec3u",     "ve3u",      "vrrc3u",  "Gec3u",
+        "veFF4f", "vE",    "verrf",    "vec4f",  "vef",       "veJJD",     "v4",      "e4k",
+        "vech",   "Jech",  "vec4h",    "ec4h",   "_KKttcH",   "vexxh",     "__qcF",   "vc4qq",
+        "33e64i", "vec4i", "6QQott4i", "v6c4i",  "zzc4O6",    "vyyc4u",    "vcZZ",    "ecWq4u",
+        "vec4u",  "vOO4u", "oYe4",     "v4",
+    };
+    for (auto _ : state) {
+        for (auto& str : kStrings) {
+            auto result = ParseTypeAlias(str);
+            benchmark::DoNotOptimize(result);
+        }
+    }
+}
+
+BENCHMARK(TypeAliasParser);
+
+}  // namespace
+}  // namespace tint::resolver
diff --git a/src/tint/resolver/type_alias_bench.cc.tmpl b/src/tint/resolver/type_alias_bench.cc.tmpl
new file mode 100644
index 0000000..3a48226
--- /dev/null
+++ b/src/tint/resolver/type_alias_bench.cc.tmpl
@@ -0,0 +1,29 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate type_alias_bench.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
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "type_alias") -}}
+
+#include "src/tint/resolver/type_alias.h"
+
+#include <array>
+
+#include "benchmark/benchmark.h"
+
+namespace tint::resolver {
+namespace {
+
+{{ Eval "BenchmarkParseEnum" $enum }}
+
+}  // namespace
+}  // namespace tint::resolver
diff --git a/src/tint/resolver/type_alias_test.cc b/src/tint/resolver/type_alias_test.cc
new file mode 100644
index 0000000..c52d44c
--- /dev/null
+++ b/src/tint/resolver/type_alias_test.cc
@@ -0,0 +1,97 @@
+// 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/resolver/type_alias_test.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/resolver/type_alias.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "src/tint/utils/string.h"
+
+namespace tint::resolver {
+namespace {
+
+namespace parse_print_tests {
+
+struct Case {
+    const char* string;
+    TypeAlias value;
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+    return out << "'" << std::string(c.string) << "'";
+}
+
+static constexpr Case kValidCases[] = {
+    {"vec2f", TypeAlias::kVec2F}, {"vec2h", TypeAlias::kVec2H}, {"vec2i", TypeAlias::kVec2I},
+    {"vec2u", TypeAlias::kVec2U}, {"vec3f", TypeAlias::kVec3F}, {"vec3h", TypeAlias::kVec3H},
+    {"vec3i", TypeAlias::kVec3I}, {"vec3u", TypeAlias::kVec3U}, {"vec4f", TypeAlias::kVec4F},
+    {"vec4h", TypeAlias::kVec4H}, {"vec4i", TypeAlias::kVec4I}, {"vec4u", TypeAlias::kVec4U},
+};
+
+static constexpr Case kInvalidCases[] = {
+    {"veccf", TypeAlias::kUndefined},     {"32", TypeAlias::kUndefined},
+    {"vVc2f", TypeAlias::kUndefined},     {"vec21", TypeAlias::kUndefined},
+    {"qqeJh", TypeAlias::kUndefined},     {"vecll7h", TypeAlias::kUndefined},
+    {"veqH2pp", TypeAlias::kUndefined},   {"vi", TypeAlias::kUndefined},
+    {"Gebi", TypeAlias::kUndefined},      {"vevi2u", TypeAlias::kUndefined},
+    {"ve8WWu", TypeAlias::kUndefined},    {"Mxxc2", TypeAlias::kUndefined},
+    {"vgg3f", TypeAlias::kUndefined},     {"V3X", TypeAlias::kUndefined},
+    {"vec33", TypeAlias::kUndefined},     {"vec3E", TypeAlias::kUndefined},
+    {"TTeP3h", TypeAlias::kUndefined},    {"vxxcdd", TypeAlias::kUndefined},
+    {"v44c3i", TypeAlias::kUndefined},    {"veVVSSi", TypeAlias::kUndefined},
+    {"22RRi", TypeAlias::kUndefined},     {"vF3u", TypeAlias::kUndefined},
+    {"vecu", TypeAlias::kUndefined},      {"ROOHVu", TypeAlias::kUndefined},
+    {"ecyf", TypeAlias::kUndefined},      {"n77rrlcGf", TypeAlias::kUndefined},
+    {"vec440", TypeAlias::kUndefined},    {"ooh", TypeAlias::kUndefined},
+    {"vezz", TypeAlias::kUndefined},      {"1ipp4h", TypeAlias::kUndefined},
+    {"XXec4i", TypeAlias::kUndefined},    {"ve9IInn5i", TypeAlias::kUndefined},
+    {"HHreSSaYi", TypeAlias::kUndefined}, {"kk4", TypeAlias::kUndefined},
+    {"jgRR", TypeAlias::kUndefined},      {"veb", TypeAlias::kUndefined},
+};
+
+using TypeAliasParseTest = testing::TestWithParam<Case>;
+
+TEST_P(TypeAliasParseTest, Parse) {
+    const char* string = GetParam().string;
+    TypeAlias expect = GetParam().value;
+    EXPECT_EQ(expect, ParseTypeAlias(string));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, TypeAliasParseTest, testing::ValuesIn(kValidCases));
+INSTANTIATE_TEST_SUITE_P(InvalidCases, TypeAliasParseTest, testing::ValuesIn(kInvalidCases));
+
+using TypeAliasPrintTest = testing::TestWithParam<Case>;
+
+TEST_P(TypeAliasPrintTest, Print) {
+    TypeAlias value = GetParam().value;
+    const char* expect = GetParam().string;
+    EXPECT_EQ(expect, utils::ToString(value));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, TypeAliasPrintTest, testing::ValuesIn(kValidCases));
+
+}  // namespace parse_print_tests
+
+}  // namespace
+}  // namespace tint::resolver
diff --git a/src/tint/resolver/type_alias_test.cc.tmpl b/src/tint/resolver/type_alias_test.cc.tmpl
new file mode 100644
index 0000000..2f37251
--- /dev/null
+++ b/src/tint/resolver/type_alias_test.cc.tmpl
@@ -0,0 +1,31 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate type_alias_test.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
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "type_alias") -}}
+
+#include "src/tint/resolver/type_alias.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "src/tint/utils/string.h"
+
+namespace tint::resolver {
+namespace {
+
+{{ Eval "TestParsePrintEnum" $enum}}
+
+}  // namespace
+}  // namespace tint::resolver
diff --git a/src/tint/resolver/type_validation_test.cc b/src/tint/resolver/type_validation_test.cc
index fe1aa05..b389fc1 100644
--- a/src/tint/resolver/type_validation_test.cc
+++ b/src/tint/resolver/type_validation_test.cc
@@ -1395,5 +1395,56 @@
                                          ParamsFor<array<2, f32>>(2)));
 }  // namespace VectorTests
 
+namespace BuiltinTypeAliasTests {
+struct Params {
+    const char* alias;
+    builder::ast_type_func_ptr type;
+};
+
+template <typename T>
+constexpr Params Case(const char* alias) {
+    return Params{alias, DataType<T>::AST};
+}
+
+using BuiltinTypeAliasTest = ResolverTestWithParam<Params>;
+TEST_P(BuiltinTypeAliasTest, CheckEquivalent) {
+    // var aliased : vecTN;
+    // var explicit : vecN<T>;
+    // explicit = aliased;
+    auto& params = GetParam();
+
+    Enable(ast::Extension::kF16);
+
+    WrapInFunction(Decl(Var("aliased", ty.type_name(params.alias))),
+                   Decl(Var("explicit", params.type(*this))),  //
+                   Assign("explicit", "aliased"));
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+TEST_P(BuiltinTypeAliasTest, Construct) {
+    // var v : vecN<T> = vecTN();
+    auto& params = GetParam();
+
+    Enable(ast::Extension::kF16);
+
+    WrapInFunction(Decl(Var("v", params.type(*this), Construct(ty.type_name(params.alias)))));
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
+                         BuiltinTypeAliasTest,
+                         testing::Values(Case<vec2<f32>>("vec2f"),
+                                         Case<vec3<f32>>("vec3f"),
+                                         Case<vec4<f32>>("vec4f"),
+                                         Case<vec2<f16>>("vec2h"),
+                                         Case<vec3<f16>>("vec3h"),
+                                         Case<vec4<f16>>("vec4h"),
+                                         Case<vec2<i32>>("vec2i"),
+                                         Case<vec3<i32>>("vec3i"),
+                                         Case<vec4<i32>>("vec4i"),
+                                         Case<vec2<u32>>("vec2u"),
+                                         Case<vec3<u32>>("vec3u"),
+                                         Case<vec4<u32>>("vec4u")));
+
+}  // namespace BuiltinTypeAliasTests
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/uniformity.h b/src/tint/resolver/uniformity.h
index 7980801..1139980 100644
--- a/src/tint/resolver/uniformity.h
+++ b/src/tint/resolver/uniformity.h
@@ -16,10 +16,10 @@
 #define SRC_TINT_RESOLVER_UNIFORMITY_H_
 
 // Forward declarations.
-namespace tint {
-namespace resolver {
+namespace tint::resolver {
 struct DependencyGraph;
-}  // namespace resolver
+}  // namespace tint::resolver
+namespace tint {
 class ProgramBuilder;
 }  // namespace tint
 
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 70e1de1..e5c7c1a 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -194,7 +194,7 @@
         [&](const sem::Matrix*) { return true; },  //
         [&](const sem::Atomic*) { return true; },
         [&](const sem::Array* arr) {
-            return !arr->IsRuntimeSized() && IsFixedFootprint(arr->ElemType());
+            return !arr->Count()->Is<sem::RuntimeArrayCount>() && IsFixedFootprint(arr->ElemType());
         },
         [&](const sem::Struct* str) {
             for (auto* member : str->Members()) {
@@ -413,7 +413,7 @@
     };
 
     auto member_name_of = [this](const sem::StructMember* sm) {
-        return symbols_.NameFor(sm->Declaration()->symbol);
+        return symbols_.NameFor(sm->Name());
     };
 
     // Only validate the [type + address space] once
@@ -446,8 +446,7 @@
 
             // Recurse into the member type.
             if (!AddressSpaceLayout(m->Type(), address_space, m->Declaration()->type->source)) {
-                AddNote("see layout of struct:\n" + str->Layout(symbols_),
-                        str->Declaration()->source);
+                AddNote("see layout of struct:\n" + str->Layout(symbols_), str->Source());
                 note_usage();
                 return false;
             }
@@ -461,14 +460,13 @@
                              " bytes, but '" + member_name_of(m) + "' is currently at offset " +
                              std::to_string(m->Offset()) + ". Consider setting @align(" +
                              std::to_string(required_align) + ") on this member",
-                         m->Declaration()->source);
+                         m->Source());
 
-                AddNote("see layout of struct:\n" + str->Layout(symbols_),
-                        str->Declaration()->source);
+                AddNote("see layout of struct:\n" + str->Layout(symbols_), str->Source());
 
                 if (auto* member_str = m->Type()->As<sem::Struct>()) {
                     AddNote("and layout of struct member:\n" + member_str->Layout(symbols_),
-                            member_str->Declaration()->source);
+                            member_str->Source());
                 }
 
                 note_usage();
@@ -488,15 +486,14 @@
                             std::to_string(prev_to_curr_offset) + " bytes between '" +
                             member_name_of(prev_member) + "' and '" + member_name_of(m) +
                             "'. Consider setting @align(16) on this member",
-                        m->Declaration()->source);
+                        m->Source());
 
-                    AddNote("see layout of struct:\n" + str->Layout(symbols_),
-                            str->Declaration()->source);
+                    AddNote("see layout of struct:\n" + str->Layout(symbols_), str->Source());
 
                     auto* prev_member_str = prev_member->Type()->As<sem::Struct>();
                     AddNote("and layout of previous member struct:\n" +
                                 prev_member_str->Layout(symbols_),
-                            prev_member_str->Declaration()->source);
+                            prev_member_str->Source());
                     note_usage();
                     return false;
                 }
@@ -800,15 +797,31 @@
     }
 
     if (auto* ref = var->Type()->As<sem::Pointer>()) {
-        auto address_space = ref->AddressSpace();
-        if (!(address_space == ast::AddressSpace::kFunction ||
-              address_space == ast::AddressSpace::kPrivate) &&
-            IsValidationEnabled(decl->attributes, ast::DisabledValidation::kIgnoreAddressSpace)) {
-            std::stringstream ss;
-            ss << "function parameter of pointer type cannot be in '" << address_space
-               << "' address space";
-            AddError(ss.str(), decl->source);
-            return false;
+        if (IsValidationEnabled(decl->attributes, ast::DisabledValidation::kIgnoreAddressSpace)) {
+            bool ok = false;
+
+            auto sc = ref->AddressSpace();
+            switch (sc) {
+                case ast::AddressSpace::kFunction:
+                case ast::AddressSpace::kPrivate:
+                    ok = true;
+                    break;
+                case ast::AddressSpace::kStorage:
+                case ast::AddressSpace::kUniform:
+                case ast::AddressSpace::kWorkgroup:
+                    ok = enabled_extensions_.Contains(
+                        ast::Extension::kChromiumExperimentalFullPtrParameters);
+                    break;
+                default:
+                    break;
+            }
+            if (!ok) {
+                std::stringstream ss;
+                ss << "function parameter of pointer type cannot be in '" << sc
+                   << "' address space";
+                AddError(ss.str(), decl->source);
+                return false;
+            }
         }
     }
 
@@ -1212,8 +1225,8 @@
         if (auto* str = ty->As<sem::Struct>()) {
             for (auto* member : str->Members()) {
                 if (!validate_entry_point_attributes_inner(
-                        member->Declaration()->attributes, member->Type(),
-                        member->Declaration()->source, param_or_ret,
+                        member->Declaration()->attributes, member->Type(), member->Source(),
+                        param_or_ret,
                         /*is_struct_member*/ true, member->Location())) {
                     AddNote("while analyzing entry point '" + symbols_.NameFor(decl->symbol) + "'",
                             decl->source);
@@ -1654,7 +1667,8 @@
             return false;
         }
 
-        if (param_type->Is<sem::Pointer>()) {
+        if (param_type->Is<sem::Pointer>() &&
+            !enabled_extensions_.Contains(ast::Extension::kChromiumExperimentalFullPtrParameters)) {
             // https://gpuweb.github.io/gpuweb/wgsl/#function-restriction
             // Each argument of pointer type to a user-defined function must have the same memory
             // view as its root identifier.
@@ -1749,12 +1763,13 @@
         }
     }
 
-    if (array_type->IsRuntimeSized()) {
+    auto* c = array_type->Count();
+    if (c->Is<sem::RuntimeArrayCount>()) {
         AddError("cannot construct a runtime-sized array", ctor->source);
         return false;
     }
 
-    if (array_type->IsOverrideSized()) {
+    if (c->IsAnyOf<sem::NamedOverrideArrayCount, sem::UnnamedOverrideArrayCount>()) {
         AddError("cannot construct an array that has an override-expression count", ctor->source);
         return false;
     }
@@ -1764,7 +1779,12 @@
         return false;
     }
 
-    const auto count = std::get<sem::ConstantArrayCount>(array_type->Count()).value;
+    if (!c->Is<sem::ConstantArrayCount>()) {
+        TINT_ICE(Resolver, diagnostics_) << "Invalid ArrayCount found";
+        return false;
+    }
+
+    const auto count = c->As<sem::ConstantArrayCount>()->value;
     if (!values.IsEmpty() && (values.Length() != count)) {
         std::string fm = values.Length() < count ? "few" : "many";
         AddError("array initializer has too " + fm + " elements: expected " +
@@ -2000,17 +2020,17 @@
 
 bool Validator::Structure(const sem::Struct* str, ast::PipelineStage stage) const {
     if (str->Members().empty()) {
-        AddError("structures must have at least one member", str->Declaration()->source);
+        AddError("structures must have at least one member", str->Source());
         return false;
     }
 
     utils::Hashset<uint32_t, 8> locations;
     for (auto* member : str->Members()) {
         if (auto* r = member->Type()->As<sem::Array>()) {
-            if (r->IsRuntimeSized()) {
+            if (r->Count()->Is<sem::RuntimeArrayCount>()) {
                 if (member != str->Members().back()) {
                     AddError("runtime arrays may only appear as the last member of a struct",
-                             member->Declaration()->source);
+                             member->Source());
                     return false;
                 }
             }
@@ -2022,7 +2042,7 @@
         } else if (!IsFixedFootprint(member->Type())) {
             AddError(
                 "a struct that contains a runtime array cannot be nested inside another struct",
-                member->Declaration()->source);
+                member->Source());
             return false;
         }
 
@@ -2041,7 +2061,7 @@
                     has_location = true;
                     TINT_ASSERT(Resolver, member->Location().has_value());
                     if (!LocationAttribute(location, member->Location().value(), member->Type(),
-                                           locations, stage, member->Declaration()->source)) {
+                                           locations, stage, member->Source())) {
                         return false;
                     }
                     return true;
@@ -2379,7 +2399,7 @@
 
 bool Validator::IsArrayWithOverrideCount(const sem::Type* ty) const {
     if (auto* arr = ty->UnwrapRef()->As<sem::Array>()) {
-        if (arr->IsOverrideSized()) {
+        if (arr->Count()->IsAnyOf<sem::NamedOverrideArrayCount, sem::UnnamedOverrideArrayCount>()) {
             return true;
         }
     }
diff --git a/src/tint/resolver/validator_is_storeable_test.cc b/src/tint/resolver/validator_is_storeable_test.cc
index 9fa064a..cd079ce 100644
--- a/src/tint/resolver/validator_is_storeable_test.cc
+++ b/src/tint/resolver/validator_is_storeable_test.cc
@@ -89,13 +89,14 @@
 }
 
 TEST_F(ValidatorIsStorableTest, ArraySizedOfStorable) {
-    auto* arr =
-        create<sem::Array>(create<sem::I32>(), sem::ConstantArrayCount{5u}, 4u, 20u, 4u, 4u);
+    auto* arr = create<sem::Array>(create<sem::I32>(), create<sem::ConstantArrayCount>(5u), 4u, 20u,
+                                   4u, 4u);
     EXPECT_TRUE(v()->IsStorable(arr));
 }
 
 TEST_F(ValidatorIsStorableTest, ArrayUnsizedOfStorable) {
-    auto* arr = create<sem::Array>(create<sem::I32>(), sem::RuntimeArrayCount{}, 4u, 4u, 4u, 4u);
+    auto* arr =
+        create<sem::Array>(create<sem::I32>(), create<sem::RuntimeArrayCount>(), 4u, 4u, 4u, 4u);
     EXPECT_TRUE(v()->IsStorable(arr));
 }
 
diff --git a/src/tint/sem/array.cc b/src/tint/sem/array.cc
index d61d430..207a627 100644
--- a/src/tint/sem/array.cc
+++ b/src/tint/sem/array.cc
@@ -28,10 +28,10 @@
 
 namespace {
 
-TypeFlags FlagsFrom(const Type* element, ArrayCount count) {
+TypeFlags FlagsFrom(const Type* element, const ArrayCount* count) {
     TypeFlags flags;
     // Only constant-expression sized arrays are constructible
-    if (std::holds_alternative<ConstantArrayCount>(count)) {
+    if (count->Is<ConstantArrayCount>()) {
         if (element->IsConstructible()) {
             flags.Add(TypeFlag::kConstructable);
         }
@@ -39,9 +39,7 @@
             flags.Add(TypeFlag::kCreationFixedFootprint);
         }
     }
-    if (std::holds_alternative<ConstantArrayCount>(count) ||
-        std::holds_alternative<NamedOverrideArrayCount>(count) ||
-        std::holds_alternative<UnnamedOverrideArrayCount>(count)) {
+    if (count->IsAnyOf<ConstantArrayCount, NamedOverrideArrayCount, UnnamedOverrideArrayCount>()) {
         if (element->HasFixedFootprint()) {
             flags.Add(TypeFlag::kFixedFootprint);
         }
@@ -56,7 +54,7 @@
     "Was the SubstituteOverride transform run?";
 
 Array::Array(const Type* element,
-             ArrayCount count,
+             const ArrayCount* count,
              uint32_t align,
              uint32_t size,
              uint32_t stride,
@@ -91,11 +89,11 @@
         out << "@stride(" << stride_ << ") ";
     }
     out << "array<" << element_->FriendlyName(symbols);
-    if (auto* const_count = std::get_if<ConstantArrayCount>(&count_)) {
+    if (auto* const_count = count_->As<ConstantArrayCount>()) {
         out << ", " << const_count->value;
-    } else if (auto* named_override_count = std::get_if<NamedOverrideArrayCount>(&count_)) {
+    } else if (auto* named_override_count = count_->As<NamedOverrideArrayCount>()) {
         out << ", " << symbols.NameFor(named_override_count->variable->Declaration()->symbol);
-    } else if (std::holds_alternative<UnnamedOverrideArrayCount>(count_)) {
+    } else if (count_->Is<UnnamedOverrideArrayCount>()) {
         out << ", [unnamed override-expression]";
     }
     out << ">";
diff --git a/src/tint/sem/array.h b/src/tint/sem/array.h
index 4047ae4..7a484e5 100644
--- a/src/tint/sem/array.h
+++ b/src/tint/sem/array.h
@@ -20,6 +20,7 @@
 #include <string>
 #include <variant>
 
+#include "src/tint/sem/array_count.h"
 #include "src/tint/sem/node.h"
 #include "src/tint/sem/type.h"
 #include "src/tint/utils/compiler_macros.h"
@@ -33,115 +34,6 @@
 
 namespace tint::sem {
 
-/// The variant of an ArrayCount when the array is a const-expression.
-/// Example:
-/// ```
-/// const N = 123;
-/// type arr = array<i32, N>
-/// ```
-struct ConstantArrayCount {
-    /// The array count constant-expression value.
-    uint32_t value;
-};
-
-/// The variant of an ArrayCount when the count is a named override variable.
-/// Example:
-/// ```
-/// override N : i32;
-/// type arr = array<i32, N>
-/// ```
-struct NamedOverrideArrayCount {
-    /// The `override` variable.
-    const GlobalVariable* variable;
-};
-
-/// The variant of an ArrayCount when the count is an unnamed override variable.
-/// Example:
-/// ```
-/// override N : i32;
-/// type arr = array<i32, N*2>
-/// ```
-struct UnnamedOverrideArrayCount {
-    /// The unnamed override expression.
-    /// Note: Each AST expression gets a unique semantic expression node, so two equivalent AST
-    /// expressions will not result in the same `expr` pointer. This property is important to ensure
-    /// that two array declarations with equivalent AST expressions do not compare equal.
-    /// For example, consider:
-    /// ```
-    /// override size : u32;
-    /// var<workgroup> a : array<f32, size * 2>;
-    /// var<workgroup> b : array<f32, size * 2>;
-    /// ```
-    // The array count for `a` and `b` have equivalent AST expressions, but the types for `a` and
-    // `b` must not compare equal.
-    const Expression* expr;
-};
-
-/// The variant of an ArrayCount when the array is is runtime-sized.
-/// Example:
-/// ```
-/// type arr = array<i32>
-/// ```
-struct RuntimeArrayCount {};
-
-/// An array count is either a constant-expression value, a named override identifier, an unnamed
-/// override identifier, or runtime-sized.
-using ArrayCount = std::variant<ConstantArrayCount,
-                                NamedOverrideArrayCount,
-                                UnnamedOverrideArrayCount,
-                                RuntimeArrayCount>;
-
-/// Equality operator
-/// @param a the LHS ConstantArrayCount
-/// @param b the RHS ConstantArrayCount
-/// @returns true if @p a is equal to @p b
-inline bool operator==(const ConstantArrayCount& a, const ConstantArrayCount& b) {
-    return a.value == b.value;
-}
-
-/// Equality operator
-/// @param a the LHS OverrideArrayCount
-/// @param b the RHS OverrideArrayCount
-/// @returns true if @p a is equal to @p b
-inline bool operator==(const NamedOverrideArrayCount& a, const NamedOverrideArrayCount& b) {
-    return a.variable == b.variable;
-}
-
-/// Equality operator
-/// @param a the LHS OverrideArrayCount
-/// @param b the RHS OverrideArrayCount
-/// @returns true if @p a is equal to @p b
-inline bool operator==(const UnnamedOverrideArrayCount& a, const UnnamedOverrideArrayCount& b) {
-    return a.expr == b.expr;
-}
-
-/// Equality operator
-/// @returns true
-inline bool operator==(const RuntimeArrayCount&, const RuntimeArrayCount&) {
-    return true;
-}
-
-/// Equality operator
-/// @param a the LHS ArrayCount
-/// @param b the RHS count
-/// @returns true if @p a is equal to @p b
-template <typename T,
-          typename = std::enable_if_t<
-              std::is_same_v<T, ConstantArrayCount> || std::is_same_v<T, NamedOverrideArrayCount> ||
-              std::is_same_v<T, UnnamedOverrideArrayCount> || std::is_same_v<T, RuntimeArrayCount>>>
-inline bool operator==(const ArrayCount& a, const T& b) {
-    TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE);
-    return std::visit(
-        [&](auto count) {
-            if constexpr (std::is_same_v<std::decay_t<decltype(count)>, T>) {
-                return count == b;
-            }
-            return false;
-        },
-        a);
-    TINT_END_DISABLE_WARNING(UNREACHABLE_CODE);
-}
-
 /// Array holds the semantic information for Array nodes.
 class Array final : public Castable<Array, Type> {
   public:
@@ -161,7 +53,7 @@
     /// of the array to the start of the next element, if there was no `@stride`
     /// attribute applied.
     Array(Type const* element,
-          ArrayCount count,
+          const ArrayCount* count,
           uint32_t align,
           uint32_t size,
           uint32_t stride,
@@ -178,11 +70,11 @@
     Type const* ElemType() const { return element_; }
 
     /// @returns the number of elements in the array.
-    const ArrayCount& Count() const { return count_; }
+    const ArrayCount* Count() const { return count_; }
 
     /// @returns the array count if the count is a const-expression, otherwise returns nullopt.
     inline std::optional<uint32_t> ConstantCount() const {
-        if (auto* count = std::get_if<ConstantArrayCount>(&count_)) {
+        if (auto* count = count_->As<ConstantArrayCount>()) {
             return count->value;
         }
         return std::nullopt;
@@ -211,36 +103,6 @@
     /// natural stride
     bool IsStrideImplicit() const { return stride_ == implicit_stride_; }
 
-    /// @returns true if this array is sized using an const-expression
-    bool IsConstantSized() const { return std::holds_alternative<ConstantArrayCount>(count_); }
-
-    /// @returns true if this array is sized using a named override variable
-    bool IsNamedOverrideSized() const {
-        return std::holds_alternative<NamedOverrideArrayCount>(count_);
-    }
-
-    /// @returns true if this array is sized using an unnamed override variable
-    bool IsUnnamedOverrideSized() const {
-        return std::holds_alternative<UnnamedOverrideArrayCount>(count_);
-    }
-
-    /// @returns true if this array is sized using a named or unnamed override variable
-    bool IsOverrideSized() const { return IsNamedOverrideSized() || IsUnnamedOverrideSized(); }
-
-    /// @returns true if this array is runtime sized
-    bool IsRuntimeSized() const { return std::holds_alternative<RuntimeArrayCount>(count_); }
-
-    /// Records that this array type (transitively) references the given override variable.
-    /// @param var the module-scope override variable
-    void AddTransitivelyReferencedOverride(const GlobalVariable* var) {
-        referenced_overrides_.Add(var);
-    }
-
-    /// @returns all transitively referenced override variables
-    const utils::UniqueVector<const GlobalVariable*, 4>& TransitivelyReferencedOverrides() const {
-        return referenced_overrides_;
-    }
-
     /// @param symbols the program's symbol table
     /// @returns the name for this type that closely resembles how it would be
     /// declared in WGSL.
@@ -248,59 +110,13 @@
 
   private:
     Type const* const element_;
-    const ArrayCount count_;
+    const ArrayCount* count_;
     const uint32_t align_;
     const uint32_t size_;
     const uint32_t stride_;
     const uint32_t implicit_stride_;
-    utils::UniqueVector<const GlobalVariable*, 4> referenced_overrides_;
 };
 
 }  // namespace tint::sem
 
-namespace std {
-
-/// Custom std::hash specialization for tint::sem::ConstantArrayCount.
-template <>
-class hash<tint::sem::ConstantArrayCount> {
-  public:
-    /// @param count the count to hash
-    /// @return the hash value
-    inline std::size_t operator()(const tint::sem::ConstantArrayCount& count) const {
-        return std::hash<decltype(count.value)>()(count.value);
-    }
-};
-
-/// Custom std::hash specialization for tint::sem::NamedOverrideArrayCount.
-template <>
-class hash<tint::sem::NamedOverrideArrayCount> {
-  public:
-    /// @param count the count to hash
-    /// @return the hash value
-    inline std::size_t operator()(const tint::sem::NamedOverrideArrayCount& count) const {
-        return std::hash<decltype(count.variable)>()(count.variable);
-    }
-};
-
-/// Custom std::hash specialization for tint::sem::UnnamedOverrideArrayCount.
-template <>
-class hash<tint::sem::UnnamedOverrideArrayCount> {
-  public:
-    /// @param count the count to hash
-    /// @return the hash value
-    inline std::size_t operator()(const tint::sem::UnnamedOverrideArrayCount& count) const {
-        return std::hash<decltype(count.expr)>()(count.expr);
-    }
-};
-
-/// Custom std::hash specialization for tint::sem::RuntimeArrayCount.
-template <>
-class hash<tint::sem::RuntimeArrayCount> {
-  public:
-    /// @return the hash value
-    inline std::size_t operator()(const tint::sem::RuntimeArrayCount&) const { return 42; }
-};
-
-}  // namespace std
-
 #endif  // SRC_TINT_SEM_ARRAY_H_
diff --git a/src/tint/sem/array_count.cc b/src/tint/sem/array_count.cc
new file mode 100644
index 0000000..fa16639
--- /dev/null
+++ b/src/tint/sem/array_count.cc
@@ -0,0 +1,82 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/sem/array_count.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::ArrayCount);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::ConstantArrayCount);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::RuntimeArrayCount);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::NamedOverrideArrayCount);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::UnnamedOverrideArrayCount);
+
+namespace tint::sem {
+
+ArrayCount::ArrayCount() : Base() {}
+ArrayCount::~ArrayCount() = default;
+
+ConstantArrayCount::ConstantArrayCount(uint32_t val) : Base(), value(val) {}
+ConstantArrayCount::~ConstantArrayCount() = default;
+
+size_t ConstantArrayCount::Hash() const {
+    return static_cast<size_t>(TypeInfo::Of<ConstantArrayCount>().full_hashcode);
+}
+
+bool ConstantArrayCount::Equals(const ArrayCount& other) const {
+    if (auto* v = other.As<ConstantArrayCount>()) {
+        return value == v->value;
+    }
+    return false;
+}
+
+RuntimeArrayCount::RuntimeArrayCount() : Base() {}
+RuntimeArrayCount::~RuntimeArrayCount() = default;
+
+size_t RuntimeArrayCount::Hash() const {
+    return static_cast<size_t>(TypeInfo::Of<RuntimeArrayCount>().full_hashcode);
+}
+
+bool RuntimeArrayCount::Equals(const ArrayCount& other) const {
+    return other.Is<RuntimeArrayCount>();
+}
+
+NamedOverrideArrayCount::NamedOverrideArrayCount(const GlobalVariable* var)
+    : Base(), variable(var) {}
+NamedOverrideArrayCount::~NamedOverrideArrayCount() = default;
+
+size_t NamedOverrideArrayCount::Hash() const {
+    return static_cast<size_t>(TypeInfo::Of<NamedOverrideArrayCount>().full_hashcode);
+}
+
+bool NamedOverrideArrayCount::Equals(const ArrayCount& other) const {
+    if (auto* v = other.As<NamedOverrideArrayCount>()) {
+        return variable == v->variable;
+    }
+    return false;
+}
+
+UnnamedOverrideArrayCount::UnnamedOverrideArrayCount(const Expression* e) : Base(), expr(e) {}
+UnnamedOverrideArrayCount::~UnnamedOverrideArrayCount() = default;
+
+size_t UnnamedOverrideArrayCount::Hash() const {
+    return static_cast<size_t>(TypeInfo::Of<UnnamedOverrideArrayCount>().full_hashcode);
+}
+
+bool UnnamedOverrideArrayCount::Equals(const ArrayCount& other) const {
+    if (auto* v = other.As<UnnamedOverrideArrayCount>()) {
+        return expr == v->expr;
+    }
+    return false;
+}
+
+}  // namespace tint::sem
diff --git a/src/tint/sem/array_count.h b/src/tint/sem/array_count.h
new file mode 100644
index 0000000..eb1a001
--- /dev/null
+++ b/src/tint/sem/array_count.h
@@ -0,0 +1,170 @@
+// 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_SEM_ARRAY_COUNT_H_
+#define SRC_TINT_SEM_ARRAY_COUNT_H_
+
+#include <functional>
+#include <string>
+
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/node.h"
+#include "src/tint/sem/variable.h"
+
+namespace tint::sem {
+
+/// An array count
+class ArrayCount : public Castable<ArrayCount, Node> {
+  public:
+    ~ArrayCount() override;
+
+    /// @returns a hash of the array count.
+    virtual size_t Hash() const = 0;
+
+    /// @param t other array count
+    /// @returns true if this array count is equal to the given array count
+    virtual bool Equals(const ArrayCount& t) const = 0;
+
+  protected:
+    ArrayCount();
+};
+
+/// The variant of an ArrayCount when the array is a const-expression.
+/// Example:
+/// ```
+/// const N = 123;
+/// type arr = array<i32, N>
+/// ```
+class ConstantArrayCount final : public Castable<ConstantArrayCount, ArrayCount> {
+  public:
+    /// Constructor
+    /// @param val the constant-expression value
+    explicit ConstantArrayCount(uint32_t val);
+    ~ConstantArrayCount() override;
+
+    /// @returns a hash of the array count.
+    size_t Hash() const override;
+
+    /// @param t other array count
+    /// @returns true if this array count is equal to the given array count
+    bool Equals(const ArrayCount& t) const override;
+
+    /// The array count constant-expression value.
+    uint32_t value;
+};
+
+/// The variant of an ArrayCount when the array is is runtime-sized.
+/// Example:
+/// ```
+/// type arr = array<i32>
+/// ```
+class RuntimeArrayCount final : public Castable<RuntimeArrayCount, ArrayCount> {
+  public:
+    /// Constructor
+    RuntimeArrayCount();
+    ~RuntimeArrayCount() override;
+
+    /// @returns a hash of the array count.
+    size_t Hash() const override;
+
+    /// @param t other array count
+    /// @returns true if this array count is equal to the given array count
+    bool Equals(const ArrayCount& t) const override;
+};
+
+/// The variant of an ArrayCount when the count is a named override variable.
+/// Example:
+/// ```
+/// override N : i32;
+/// type arr = array<i32, N>
+/// ```
+class NamedOverrideArrayCount final : public Castable<NamedOverrideArrayCount, ArrayCount> {
+  public:
+    /// Constructor
+    /// @param var the `override` variable
+    explicit NamedOverrideArrayCount(const GlobalVariable* var);
+    ~NamedOverrideArrayCount() override;
+
+    /// @returns a hash of the array count.
+    size_t Hash() const override;
+
+    /// @param t other array count
+    /// @returns true if this array count is equal to the given array count
+    bool Equals(const ArrayCount& t) const override;
+
+    /// The `override` variable.
+    const GlobalVariable* variable;
+};
+
+/// The variant of an ArrayCount when the count is an unnamed override variable.
+/// Example:
+/// ```
+/// override N : i32;
+/// type arr = array<i32, N*2>
+/// ```
+class UnnamedOverrideArrayCount final : public Castable<UnnamedOverrideArrayCount, ArrayCount> {
+  public:
+    /// Constructor
+    /// @param e the override expression
+    explicit UnnamedOverrideArrayCount(const Expression* e);
+    ~UnnamedOverrideArrayCount() override;
+
+    /// @returns a hash of the array count.
+    size_t Hash() const override;
+
+    /// @param t other array count
+    /// @returns true if this array count is equal to the given array count
+    bool Equals(const ArrayCount& t) const override;
+
+    /// The unnamed override expression.
+    /// Note: Each AST expression gets a unique semantic expression node, so two equivalent AST
+    /// expressions will not result in the same `expr` pointer. This property is important to ensure
+    /// that two array declarations with equivalent AST expressions do not compare equal.
+    /// For example, consider:
+    /// ```
+    /// override size : u32;
+    /// var<workgroup> a : array<f32, size * 2>;
+    /// var<workgroup> b : array<f32, size * 2>;
+    /// ```
+    // The array count for `a` and `b` have equivalent AST expressions, but the types for `a` and
+    // `b` must not compare equal.
+    const Expression* expr;
+};
+
+}  // namespace tint::sem
+
+namespace std {
+
+/// std::hash specialization for tint::sem::ArrayCount
+template <>
+struct hash<tint::sem::ArrayCount> {
+    /// @param a the array count to obtain a hash from
+    /// @returns the hash of the array count
+    size_t operator()(const tint::sem::ArrayCount& a) const { return a.Hash(); }
+};
+
+/// std::equal_to specialization for tint::sem::ArrayCount
+template <>
+struct equal_to<tint::sem::ArrayCount> {
+    /// @param a the first array count to compare
+    /// @param b the second array count to compare
+    /// @returns true if the two array counts are equal
+    bool operator()(const tint::sem::ArrayCount& a, const tint::sem::ArrayCount& b) const {
+        return a.Equals(b);
+    }
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_SEM_ARRAY_COUNT_H_
diff --git a/src/tint/sem/array_test.cc b/src/tint/sem/array_test.cc
index a51b492..a2bc3d3 100644
--- a/src/tint/sem/array_test.cc
+++ b/src/tint/sem/array_test.cc
@@ -21,22 +21,22 @@
 using ArrayTest = TestHelper;
 
 TEST_F(ArrayTest, CreateSizedArray) {
-    auto* a = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
-    auto* b = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
-    auto* c = create<Array>(create<U32>(), ConstantArrayCount{3u}, 4u, 8u, 32u, 16u);
-    auto* d = create<Array>(create<U32>(), ConstantArrayCount{2u}, 5u, 8u, 32u, 16u);
-    auto* e = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 9u, 32u, 16u);
-    auto* f = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 33u, 16u);
-    auto* g = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 33u, 17u);
+    auto* a = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* b = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* c = create<Array>(create<U32>(), create<ConstantArrayCount>(3u), 4u, 8u, 32u, 16u);
+    auto* d = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 5u, 8u, 32u, 16u);
+    auto* e = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 9u, 32u, 16u);
+    auto* f = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 16u);
+    auto* g = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 17u);
 
     EXPECT_EQ(a->ElemType(), create<U32>());
-    EXPECT_EQ(a->Count(), ConstantArrayCount{2u});
+    EXPECT_EQ(a->Count(), create<ConstantArrayCount>(2u));
     EXPECT_EQ(a->Align(), 4u);
     EXPECT_EQ(a->Size(), 8u);
     EXPECT_EQ(a->Stride(), 32u);
     EXPECT_EQ(a->ImplicitStride(), 16u);
     EXPECT_FALSE(a->IsStrideImplicit());
-    EXPECT_FALSE(a->IsRuntimeSized());
+    EXPECT_FALSE(a->Count()->Is<RuntimeArrayCount>());
 
     EXPECT_EQ(a, b);
     EXPECT_NE(a, c);
@@ -47,21 +47,21 @@
 }
 
 TEST_F(ArrayTest, CreateRuntimeArray) {
-    auto* a = create<Array>(create<U32>(), RuntimeArrayCount{}, 4u, 8u, 32u, 32u);
-    auto* b = create<Array>(create<U32>(), RuntimeArrayCount{}, 4u, 8u, 32u, 32u);
-    auto* c = create<Array>(create<U32>(), RuntimeArrayCount{}, 5u, 8u, 32u, 32u);
-    auto* d = create<Array>(create<U32>(), RuntimeArrayCount{}, 4u, 9u, 32u, 32u);
-    auto* e = create<Array>(create<U32>(), RuntimeArrayCount{}, 4u, 8u, 33u, 32u);
-    auto* f = create<Array>(create<U32>(), RuntimeArrayCount{}, 4u, 8u, 33u, 17u);
+    auto* a = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 32u);
+    auto* b = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 32u);
+    auto* c = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 5u, 8u, 32u, 32u);
+    auto* d = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 9u, 32u, 32u);
+    auto* e = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 33u, 32u);
+    auto* f = create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 33u, 17u);
 
     EXPECT_EQ(a->ElemType(), create<U32>());
-    EXPECT_EQ(a->Count(), sem::RuntimeArrayCount{});
+    EXPECT_EQ(a->Count(), create<sem::RuntimeArrayCount>());
     EXPECT_EQ(a->Align(), 4u);
     EXPECT_EQ(a->Size(), 8u);
     EXPECT_EQ(a->Stride(), 32u);
     EXPECT_EQ(a->ImplicitStride(), 32u);
     EXPECT_TRUE(a->IsStrideImplicit());
-    EXPECT_TRUE(a->IsRuntimeSized());
+    EXPECT_TRUE(a->Count()->Is<RuntimeArrayCount>());
 
     EXPECT_EQ(a, b);
     EXPECT_NE(a, c);
@@ -71,13 +71,13 @@
 }
 
 TEST_F(ArrayTest, Hash) {
-    auto* a = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
-    auto* b = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
-    auto* c = create<Array>(create<U32>(), ConstantArrayCount{3u}, 4u, 8u, 32u, 16u);
-    auto* d = create<Array>(create<U32>(), ConstantArrayCount{2u}, 5u, 8u, 32u, 16u);
-    auto* e = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 9u, 32u, 16u);
-    auto* f = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 33u, 16u);
-    auto* g = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 33u, 17u);
+    auto* a = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* b = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* c = create<Array>(create<U32>(), create<ConstantArrayCount>(3u), 4u, 8u, 32u, 16u);
+    auto* d = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 5u, 8u, 32u, 16u);
+    auto* e = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 9u, 32u, 16u);
+    auto* f = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 16u);
+    auto* g = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 17u);
 
     EXPECT_EQ(a->Hash(), b->Hash());
     EXPECT_NE(a->Hash(), c->Hash());
@@ -88,13 +88,13 @@
 }
 
 TEST_F(ArrayTest, Equals) {
-    auto* a = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
-    auto* b = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
-    auto* c = create<Array>(create<U32>(), ConstantArrayCount{3u}, 4u, 8u, 32u, 16u);
-    auto* d = create<Array>(create<U32>(), ConstantArrayCount{2u}, 5u, 8u, 32u, 16u);
-    auto* e = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 9u, 32u, 16u);
-    auto* f = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 33u, 16u);
-    auto* g = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 33u, 17u);
+    auto* a = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* b = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
+    auto* c = create<Array>(create<U32>(), create<ConstantArrayCount>(3u), 4u, 8u, 32u, 16u);
+    auto* d = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 5u, 8u, 32u, 16u);
+    auto* e = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 9u, 32u, 16u);
+    auto* f = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 16u);
+    auto* g = create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 33u, 17u);
 
     EXPECT_TRUE(a->Equals(*b));
     EXPECT_FALSE(a->Equals(*c));
@@ -106,32 +106,34 @@
 }
 
 TEST_F(ArrayTest, FriendlyNameRuntimeSized) {
-    auto* arr = create<Array>(create<I32>(), RuntimeArrayCount{}, 0u, 4u, 4u, 4u);
+    auto* arr = create<Array>(create<I32>(), create<RuntimeArrayCount>(), 0u, 4u, 4u, 4u);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>");
 }
 
 TEST_F(ArrayTest, FriendlyNameStaticSized) {
-    auto* arr = create<Array>(create<I32>(), ConstantArrayCount{5u}, 4u, 20u, 4u, 4u);
+    auto* arr = create<Array>(create<I32>(), create<ConstantArrayCount>(5u), 4u, 20u, 4u, 4u);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>");
 }
 
 TEST_F(ArrayTest, FriendlyNameRuntimeSizedNonImplicitStride) {
-    auto* arr = create<Array>(create<I32>(), RuntimeArrayCount{}, 0u, 4u, 8u, 4u);
+    auto* arr = create<Array>(create<I32>(), create<RuntimeArrayCount>(), 0u, 4u, 8u, 4u);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(8) array<i32>");
 }
 
 TEST_F(ArrayTest, FriendlyNameStaticSizedNonImplicitStride) {
-    auto* arr = create<Array>(create<I32>(), ConstantArrayCount{5u}, 4u, 20u, 8u, 4u);
+    auto* arr = create<Array>(create<I32>(), create<ConstantArrayCount>(5u), 4u, 20u, 8u, 4u);
     EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(8) array<i32, 5>");
 }
 
 TEST_F(ArrayTest, IsConstructable) {
-    auto* fixed_sized = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
+    auto* fixed_sized =
+        create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
     auto* named_override_sized =
-        create<Array>(create<U32>(), NamedOverrideArrayCount{}, 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<NamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
     auto* unnamed_override_sized =
-        create<Array>(create<U32>(), UnnamedOverrideArrayCount{}, 4u, 8u, 32u, 16u);
-    auto* runtime_sized = create<Array>(create<U32>(), RuntimeArrayCount{}, 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<UnnamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
+    auto* runtime_sized =
+        create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 16u);
 
     EXPECT_TRUE(fixed_sized->IsConstructible());
     EXPECT_FALSE(named_override_sized->IsConstructible());
@@ -140,12 +142,14 @@
 }
 
 TEST_F(ArrayTest, HasCreationFixedFootprint) {
-    auto* fixed_sized = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
+    auto* fixed_sized =
+        create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
     auto* named_override_sized =
-        create<Array>(create<U32>(), NamedOverrideArrayCount{}, 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<NamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
     auto* unnamed_override_sized =
-        create<Array>(create<U32>(), UnnamedOverrideArrayCount{}, 4u, 8u, 32u, 16u);
-    auto* runtime_sized = create<Array>(create<U32>(), RuntimeArrayCount{}, 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<UnnamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
+    auto* runtime_sized =
+        create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 16u);
 
     EXPECT_TRUE(fixed_sized->HasCreationFixedFootprint());
     EXPECT_FALSE(named_override_sized->HasCreationFixedFootprint());
@@ -154,12 +158,14 @@
 }
 
 TEST_F(ArrayTest, HasFixedFootprint) {
-    auto* fixed_sized = create<Array>(create<U32>(), ConstantArrayCount{2u}, 4u, 8u, 32u, 16u);
+    auto* fixed_sized =
+        create<Array>(create<U32>(), create<ConstantArrayCount>(2u), 4u, 8u, 32u, 16u);
     auto* named_override_sized =
-        create<Array>(create<U32>(), NamedOverrideArrayCount{}, 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<NamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
     auto* unnamed_override_sized =
-        create<Array>(create<U32>(), UnnamedOverrideArrayCount{}, 4u, 8u, 32u, 16u);
-    auto* runtime_sized = create<Array>(create<U32>(), RuntimeArrayCount{}, 4u, 8u, 32u, 16u);
+        create<Array>(create<U32>(), create<UnnamedOverrideArrayCount>(nullptr), 4u, 8u, 32u, 16u);
+    auto* runtime_sized =
+        create<Array>(create<U32>(), create<RuntimeArrayCount>(), 4u, 8u, 32u, 16u);
 
     EXPECT_TRUE(fixed_sized->HasFixedFootprint());
     EXPECT_TRUE(named_override_sized->HasFixedFootprint());
diff --git a/src/tint/sem/function.h b/src/tint/sem/function.h
index 973aa43..8f21f74 100644
--- a/src/tint/sem/function.h
+++ b/src/tint/sem/function.h
@@ -141,7 +141,7 @@
 
     /// @returns the list of direct calls to functions / builtins made by this
     /// function
-    std::vector<const Call*> DirectCallStatements() const { return direct_calls_; }
+    std::vector<const Call*> DirectCalls() const { return direct_calls_; }
 
     /// Adds a record of the direct function / builtin calls made by this
     /// function
@@ -160,7 +160,7 @@
         return nullptr;
     }
 
-    /// @returns the list of callsites of this function
+    /// @returns the list of callsites to this function
     std::vector<const Call*> CallSites() const { return callsites_; }
 
     /// Adds a record of a callsite to this function
diff --git a/src/tint/sem/info.h b/src/tint/sem/info.h
index 894b408..28b41a6 100644
--- a/src/tint/sem/info.h
+++ b/src/tint/sem/info.h
@@ -24,6 +24,7 @@
 #include "src/tint/debug.h"
 #include "src/tint/sem/node.h"
 #include "src/tint/sem/type_mappings.h"
+#include "src/tint/utils/unique_vector.h"
 
 // Forward declarations
 namespace tint::sem {
@@ -44,6 +45,9 @@
     using GetResultType =
         std::conditional_t<std::is_same<SEM, InferFromAST>::value, SemanticNodeTypeFor<AST>, SEM>;
 
+    /// Alias to a unique vector of transitively referenced global variables
+    using TransitivelyReferenced = utils::UniqueVector<const GlobalVariable*, 4>;
+
     /// Constructor
     Info();
 
@@ -117,9 +121,30 @@
     /// @returns the semantic module.
     const sem::Module* Module() const { return module_; }
 
+    /// Records that this variable (transitively) references the given override variable.
+    /// @param from the item the variable is referenced from
+    /// @param var the module-scope override variable
+    void AddTransitivelyReferencedOverride(const CastableBase* from, const GlobalVariable* var) {
+        if (referenced_overrides_.count(from) == 0) {
+            referenced_overrides_.insert({from, TransitivelyReferenced{}});
+        }
+        referenced_overrides_[from].Add(var);
+    }
+
+    /// @param from the key to look up
+    /// @returns all transitively referenced override variables or nullptr if none set
+    const TransitivelyReferenced* TransitivelyReferencedOverrides(const CastableBase* from) const {
+        if (referenced_overrides_.count(from) == 0) {
+            return nullptr;
+        }
+        return &referenced_overrides_.at(from);
+    }
+
   private:
     // AST node index to semantic node
     std::vector<const sem::Node*> nodes_;
+    // Lists transitively referenced overrides for the given item
+    std::unordered_map<const CastableBase*, TransitivelyReferenced> referenced_overrides_;
     // The semantic module
     sem::Module* module_ = nullptr;
 };
diff --git a/src/tint/sem/struct.cc b/src/tint/sem/struct.cc
index d80a04a..c46e310 100644
--- a/src/tint/sem/struct.cc
+++ b/src/tint/sem/struct.cc
@@ -52,6 +52,7 @@
 }  // namespace
 
 Struct::Struct(const ast::Struct* declaration,
+               tint::Source source,
                Symbol name,
                StructMemberList members,
                uint32_t align,
@@ -59,6 +60,7 @@
                uint32_t size_no_padding)
     : Base(FlagsFrom(members)),
       declaration_(declaration),
+      source_(source),
       name_(name),
       members_(std::move(members)),
       align_(align),
@@ -80,7 +82,7 @@
 
 const StructMember* Struct::FindMember(Symbol name) const {
     for (auto* member : members_) {
-        if (member->Declaration()->symbol == name) {
+        if (member->Name() == name) {
             return member;
         }
     }
@@ -102,9 +104,7 @@
 std::string Struct::Layout(const tint::SymbolTable& symbols) const {
     std::stringstream ss;
 
-    auto member_name_of = [&](const sem::StructMember* sm) {
-        return symbols.NameFor(sm->Declaration()->symbol);
-    };
+    auto member_name_of = [&](const sem::StructMember* sm) { return symbols.NameFor(sm->Name()); };
 
     if (Members().empty()) {
         return {};
@@ -168,6 +168,7 @@
 }
 
 StructMember::StructMember(const ast::StructMember* declaration,
+                           tint::Source source,
                            Symbol name,
                            const sem::Type* type,
                            uint32_t index,
@@ -176,6 +177,7 @@
                            uint32_t size,
                            std::optional<uint32_t> location)
     : declaration_(declaration),
+      source_(source),
       name_(name),
       type_(type),
       index_(index),
diff --git a/src/tint/sem/struct.h b/src/tint/sem/struct.h
index 0ddfa23..831cf3e 100644
--- a/src/tint/sem/struct.h
+++ b/src/tint/sem/struct.h
@@ -57,6 +57,7 @@
   public:
     /// Constructor
     /// @param declaration the AST structure declaration
+    /// @param source the source of the structure
     /// @param name the name of the structure
     /// @param members the structure members
     /// @param align the byte alignment of the structure
@@ -64,6 +65,7 @@
     /// @param size_no_padding size of the members without the end of structure
     /// alignment padding
     Struct(const ast::Struct* declaration,
+           tint::Source source,
            Symbol name,
            StructMemberList members,
            uint32_t align,
@@ -83,6 +85,9 @@
     /// @returns the struct
     const ast::Struct* Declaration() const { return declaration_; }
 
+    /// @returns the source of the structure
+    tint::Source Source() const { return source_; }
+
     /// @returns the name of the structure
     Symbol Name() const { return name_; }
 
@@ -162,6 +167,7 @@
 
   private:
     ast::Struct const* const declaration_;
+    const tint::Source source_;
     const Symbol name_;
     const StructMemberList members_;
     const uint32_t align_;
@@ -177,7 +183,8 @@
   public:
     /// Constructor
     /// @param declaration the AST declaration node
-    /// @param name the name of the structure
+    /// @param source the source of the struct member
+    /// @param name the name of the structure member
     /// @param type the type of the member
     /// @param index the index of the member in the structure
     /// @param offset the byte offset from the base of the structure
@@ -185,6 +192,7 @@
     /// @param size the byte size of the member
     /// @param location the location attribute, if present
     StructMember(const ast::StructMember* declaration,
+                 tint::Source source,
                  Symbol name,
                  const sem::Type* type,
                  uint32_t index,
@@ -199,6 +207,9 @@
     /// @returns the AST declaration node
     const ast::StructMember* Declaration() const { return declaration_; }
 
+    /// @returns the source the struct member
+    const tint::Source& Source() const { return source_; }
+
     /// @returns the name of the structure member
     Symbol Name() const { return name_; }
 
@@ -229,6 +240,7 @@
 
   private:
     const ast::StructMember* const declaration_;
+    const tint::Source source_;
     const Symbol name_;
     const sem::Struct* struct_;
     const sem::Type* type_;
diff --git a/src/tint/sem/struct_test.cc b/src/tint/sem/struct_test.cc
index a8b36ca..6a88307 100644
--- a/src/tint/sem/struct_test.cc
+++ b/src/tint/sem/struct_test.cc
@@ -26,8 +26,8 @@
     auto name = Sym("S");
     auto* impl = create<ast::Struct>(name, utils::Empty, utils::Empty);
     auto* ptr = impl;
-    auto* s = create<sem::Struct>(impl, impl->name, StructMemberList{}, 4u /* align */,
-                                  8u /* size */, 16u /* size_no_padding */);
+    auto* s = create<sem::Struct>(impl, impl->source, impl->name, StructMemberList{},
+                                  4u /* align */, 8u /* size */, 16u /* size_no_padding */);
     EXPECT_EQ(s->Declaration(), ptr);
     EXPECT_EQ(s->Align(), 4u);
     EXPECT_EQ(s->Size(), 8u);
@@ -36,22 +36,22 @@
 
 TEST_F(StructTest, Hash) {
     auto* a_impl = create<ast::Struct>(Sym("a"), utils::Empty, utils::Empty);
-    auto* a = create<sem::Struct>(a_impl, a_impl->name, StructMemberList{}, 4u /* align */,
-                                  4u /* size */, 4u /* size_no_padding */);
+    auto* a = create<sem::Struct>(a_impl, a_impl->source, a_impl->name, StructMemberList{},
+                                  4u /* align */, 4u /* size */, 4u /* size_no_padding */);
     auto* b_impl = create<ast::Struct>(Sym("b"), utils::Empty, utils::Empty);
-    auto* b = create<sem::Struct>(b_impl, b_impl->name, StructMemberList{}, 4u /* align */,
-                                  4u /* size */, 4u /* size_no_padding */);
+    auto* b = create<sem::Struct>(b_impl, b_impl->source, b_impl->name, StructMemberList{},
+                                  4u /* align */, 4u /* size */, 4u /* size_no_padding */);
 
     EXPECT_NE(a->Hash(), b->Hash());
 }
 
 TEST_F(StructTest, Equals) {
     auto* a_impl = create<ast::Struct>(Sym("a"), utils::Empty, utils::Empty);
-    auto* a = create<sem::Struct>(a_impl, a_impl->name, StructMemberList{}, 4u /* align */,
-                                  4u /* size */, 4u /* size_no_padding */);
+    auto* a = create<sem::Struct>(a_impl, a_impl->source, a_impl->name, StructMemberList{},
+                                  4u /* align */, 4u /* size */, 4u /* size_no_padding */);
     auto* b_impl = create<ast::Struct>(Sym("b"), utils::Empty, utils::Empty);
-    auto* b = create<sem::Struct>(b_impl, b_impl->name, StructMemberList{}, 4u /* align */,
-                                  4u /* size */, 4u /* size_no_padding */);
+    auto* b = create<sem::Struct>(b_impl, b_impl->source, b_impl->name, StructMemberList{},
+                                  4u /* align */, 4u /* size */, 4u /* size_no_padding */);
 
     EXPECT_TRUE(a->Equals(*a));
     EXPECT_FALSE(a->Equals(*b));
@@ -61,8 +61,8 @@
 TEST_F(StructTest, FriendlyName) {
     auto name = Sym("my_struct");
     auto* impl = create<ast::Struct>(name, utils::Empty, utils::Empty);
-    auto* s = create<sem::Struct>(impl, impl->name, StructMemberList{}, 4u /* align */,
-                                  4u /* size */, 4u /* size_no_padding */);
+    auto* s = create<sem::Struct>(impl, impl->source, impl->name, StructMemberList{},
+                                  4u /* align */, 4u /* size */, 4u /* size_no_padding */);
     EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
 }
 
diff --git a/src/tint/sem/type.cc b/src/tint/sem/type.cc
index f9db53b..3d25e7e 100644
--- a/src/tint/sem/type.cc
+++ b/src/tint/sem/type.cc
@@ -273,7 +273,7 @@
         },
         [&](const Array* a) {
             if (count) {
-                if (auto* const_count = std::get_if<ConstantArrayCount>(&a->Count())) {
+                if (auto* const_count = a->Count()->As<ConstantArrayCount>()) {
                     *count = const_count->value;
                 }
             }
diff --git a/src/tint/sem/type_manager.h b/src/tint/sem/type_manager.h
index 636b7a0..33c42b3 100644
--- a/src/tint/sem/type_manager.h
+++ b/src/tint/sem/type_manager.h
@@ -19,16 +19,17 @@
 #include <unordered_map>
 #include <utility>
 
+#include "src/tint/sem/array_count.h"
 #include "src/tint/sem/type.h"
 #include "src/tint/utils/unique_allocator.h"
 
 namespace tint::sem {
 
 /// The type manager holds all the pointers to the known types.
-class TypeManager final : public utils::UniqueAllocator<Type> {
+class TypeManager final {
   public:
     /// Iterator is the type returned by begin() and end()
-    using Iterator = utils::BlockAllocator<Type>::ConstIterator;
+    using TypeIterator = utils::BlockAllocator<Type>::ConstIterator;
 
     /// Constructor
     TypeManager();
@@ -55,30 +56,51 @@
     /// @return the Manager that wraps `inner`
     static TypeManager Wrap(const TypeManager& inner) {
         TypeManager out;
-        out.items = inner.items;
+        out.types_.Wrap(inner.types_);
+        out.array_counts_.Wrap(inner.array_counts_);
         return out;
     }
 
-    /// @param args the arguments used to create the temporary type used for the search.
-    /// @return a pointer to an instance of `T` with the provided arguments, or nullptr if the type
+    /// @param args the arguments used to construct the object.
+    /// @return a pointer to an instance of `T` with the provided arguments.
+    ///         If an existing instance of `T` has been constructed, then the same
+    ///         pointer is returned.
+    template <typename TYPE,
+              typename _ = std::enable_if<traits::IsTypeOrDerived<TYPE, sem::Type>>,
+              typename... ARGS>
+    TYPE* Get(ARGS&&... args) {
+        return types_.Get<TYPE>(std::forward<ARGS>(args)...);
+    }
+
+    /// @param args the arguments used to create the temporary used for the search.
+    /// @return a pointer to an instance of `T` with the provided arguments, or nullptr if the item
     ///         was not found.
-    template <typename TYPE, typename... ARGS>
+    template <typename TYPE,
+              typename _ = std::enable_if<traits::IsTypeOrDerived<TYPE, sem::Type>>,
+              typename... ARGS>
     TYPE* Find(ARGS&&... args) const {
-        // Create a temporary T instance on the stack so that we can hash it, and
-        // use it for equality lookup for the std::unordered_set.
-        TYPE key{args...};
-        auto hash = Hasher{}(key);
-        auto it = items.find(Entry{hash, &key});
-        if (it != items.end()) {
-            return static_cast<TYPE*>(it->ptr);
-        }
-        return nullptr;
+        return types_.Find<TYPE>(std::forward<ARGS>(args)...);
+    }
+
+    /// @param args the arguments used to construct the object.
+    /// @return a pointer to an instance of `T` with the provided arguments.
+    ///         If an existing instance of `T` has been constructed, then the same
+    ///         pointer is returned.
+    template <typename TYPE,
+              typename _ = std::enable_if<traits::IsTypeOrDerived<TYPE, sem::ArrayCount>>,
+              typename... ARGS>
+    TYPE* GetArrayCount(ARGS&&... args) {
+        return array_counts_.Get<TYPE>(std::forward<ARGS>(args)...);
     }
 
     /// @returns an iterator to the beginning of the types
-    Iterator begin() const { return allocator.Objects().begin(); }
+    TypeIterator begin() const { return types_.begin(); }
     /// @returns an iterator to the end of the types
-    Iterator end() const { return allocator.Objects().end(); }
+    TypeIterator end() const { return types_.end(); }
+
+  private:
+    utils::UniqueAllocator<Type> types_;
+    utils::UniqueAllocator<ArrayCount> array_counts_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/type_test.cc b/src/tint/sem/type_test.cc
index aec9bde..9148322 100644
--- a/src/tint/sem/type_test.cc
+++ b/src/tint/sem/type_test.cc
@@ -45,10 +45,12 @@
     const sem::Reference* ref_u32 =
         create<Reference>(u32, ast::AddressSpace::kPrivate, ast::Access::kReadWrite);
     const sem::Struct* str_f32 = create<Struct>(nullptr,
+                                                Source{},
                                                 Sym("str_f32"),
                                                 StructMemberList{
                                                     create<StructMember>(
                                                         /* declaration */ nullptr,
+                                                        /* source */ Source{},
                                                         /* name */ Sym("x"),
                                                         /* type */ f32,
                                                         /* index */ 0u,
@@ -61,10 +63,12 @@
                                                 /* size*/ 4u,
                                                 /* size_no_padding*/ 4u);
     const sem::Struct* str_f16 = create<Struct>(nullptr,
+                                                Source{},
                                                 Sym("str_f16"),
                                                 StructMemberList{
                                                     create<StructMember>(
                                                         /* declaration */ nullptr,
+                                                        /* source */ Source{},
                                                         /* name */ Sym("x"),
                                                         /* type */ f16,
                                                         /* index */ 0u,
@@ -77,10 +81,12 @@
                                                 /* size*/ 4u,
                                                 /* size_no_padding*/ 4u);
     sem::Struct* str_af = create<Struct>(nullptr,
+                                         Source{},
                                          Sym("str_af"),
                                          StructMemberList{
                                              create<StructMember>(
                                                  /* declaration */ nullptr,
+                                                 /* source */ Source{},
                                                  /* name */ Sym("x"),
                                                  /* type */ af,
                                                  /* index */ 0u,
@@ -94,63 +100,63 @@
                                          /* size_no_padding*/ 4u);
     const sem::Array* arr_i32 = create<Array>(
         /* element */ i32,
-        /* count */ ConstantArrayCount{5u},
+        /* count */ create<ConstantArrayCount>(5u),
         /* align */ 4u,
         /* size */ 5u * 4u,
         /* stride */ 5u * 4u,
         /* implicit_stride */ 5u * 4u);
     const sem::Array* arr_ai = create<Array>(
         /* element */ ai,
-        /* count */ ConstantArrayCount{5u},
+        /* count */ create<ConstantArrayCount>(5u),
         /* align */ 4u,
         /* size */ 5u * 4u,
         /* stride */ 5u * 4u,
         /* implicit_stride */ 5u * 4u);
     const sem::Array* arr_vec3_i32 = create<Array>(
         /* element */ vec3_i32,
-        /* count */ ConstantArrayCount{5u},
+        /* count */ create<ConstantArrayCount>(5u),
         /* align */ 16u,
         /* size */ 5u * 16u,
         /* stride */ 5u * 16u,
         /* implicit_stride */ 5u * 16u);
     const sem::Array* arr_vec3_ai = create<Array>(
         /* element */ vec3_ai,
-        /* count */ ConstantArrayCount{5u},
+        /* count */ create<ConstantArrayCount>(5u),
         /* align */ 16u,
         /* size */ 5u * 16u,
         /* stride */ 5u * 16u,
         /* implicit_stride */ 5u * 16u);
     const sem::Array* arr_mat4x3_f16 = create<Array>(
         /* element */ mat4x3_f16,
-        /* count */ ConstantArrayCount{5u},
+        /* count */ create<ConstantArrayCount>(5u),
         /* align */ 32u,
         /* size */ 5u * 32u,
         /* stride */ 5u * 32u,
         /* implicit_stride */ 5u * 32u);
     const sem::Array* arr_mat4x3_f32 = create<Array>(
         /* element */ mat4x3_f32,
-        /* count */ ConstantArrayCount{5u},
+        /* count */ create<ConstantArrayCount>(5u),
         /* align */ 64u,
         /* size */ 5u * 64u,
         /* stride */ 5u * 64u,
         /* implicit_stride */ 5u * 64u);
     const sem::Array* arr_mat4x3_af = create<Array>(
         /* element */ mat4x3_af,
-        /* count */ ConstantArrayCount{5u},
+        /* count */ create<ConstantArrayCount>(5u),
         /* align */ 64u,
         /* size */ 5u * 64u,
         /* stride */ 5u * 64u,
         /* implicit_stride */ 5u * 64u);
     const sem::Array* arr_str_f16 = create<Array>(
         /* element */ str_f16,
-        /* count */ ConstantArrayCount{5u},
+        /* count */ create<ConstantArrayCount>(5u),
         /* align */ 4u,
         /* size */ 5u * 4u,
         /* stride */ 5u * 4u,
         /* implicit_stride */ 5u * 4u);
     const sem::Array* arr_str_af = create<Array>(
         /* element */ str_af,
-        /* count */ ConstantArrayCount{5u},
+        /* count */ create<ConstantArrayCount>(5u),
         /* align */ 4u,
         /* size */ 5u * 4u,
         /* stride */ 5u * 4u,
diff --git a/src/tint/sem/variable.h b/src/tint/sem/variable.h
index 08ffe99..b977797 100644
--- a/src/tint/sem/variable.h
+++ b/src/tint/sem/variable.h
@@ -183,23 +183,11 @@
     /// @returns the location value for the parameter, if set
     std::optional<uint32_t> Location() const { return location_; }
 
-    /// Records that this variable (transitively) references the given override variable.
-    /// @param var the module-scope override variable
-    void AddTransitivelyReferencedOverride(const GlobalVariable* var) {
-        referenced_overrides_.Add(var);
-    }
-
-    /// @returns all transitively referenced override variables
-    const utils::UniqueVector<const GlobalVariable*, 4>& TransitivelyReferencedOverrides() const {
-        return referenced_overrides_;
-    }
-
   private:
     const sem::BindingPoint binding_point_;
 
     tint::OverrideId override_id_;
     std::optional<uint32_t> location_;
-    utils::UniqueVector<const GlobalVariable*, 4> referenced_overrides_;
 };
 
 /// Parameter is a function parameter
@@ -231,7 +219,7 @@
         return static_cast<const ast::Parameter*>(Variable::Declaration());
     }
 
-    /// @return the index of the parmeter in the function
+    /// @return the index of the parameter in the function
     uint32_t Index() const { return index_; }
 
     /// @returns the semantic usage for the parameter
diff --git a/src/tint/traits.h b/src/tint/traits.h
index 8dcc65b..441067e 100644
--- a/src/tint/traits.h
+++ b/src/tint/traits.h
@@ -160,6 +160,22 @@
 using SliceTuple =
     std::remove_pointer_t<decltype(detail::SwizzlePtrTy<TUPLE>(Range<OFFSET, COUNT>()))>;
 
+namespace detail {
+/// Base template for IsTypeIn
+template <class T, class TypeList>
+struct IsTypeIn;
+
+/// Specialization for IsTypeIn
+template <class T, template <class...> class TypeContainer, class... Ts>
+struct IsTypeIn<T, TypeContainer<Ts...>> : std::disjunction<std::is_same<T, Ts>...> {};
+}  // namespace detail
+
+/// Evaluates to true if T is one of the types in the TypeContainer's template arguments.
+/// Works for std::variant, std::tuple, std::pair, or any class template where all parameters are
+/// types.
+template <typename T, typename TypeContainer>
+static constexpr bool IsTypeIn = detail::IsTypeIn<T, TypeContainer>::value;
+
 }  // namespace tint::traits
 
 #endif  // SRC_TINT_TRAITS_H_
diff --git a/src/tint/transform/builtin_polyfill.cc b/src/tint/transform/builtin_polyfill.cc
index 15aa233..62f8f4c 100644
--- a/src/tint/transform/builtin_polyfill.cc
+++ b/src/tint/transform/builtin_polyfill.cc
@@ -301,8 +301,18 @@
             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"))));
-                body.Push(
-                    b.Return(b.Shr(b.Shl("v", vecN_u32(b.Expr("shl"))), vecN_u32(b.Expr("shr")))));
+                // Here we don't want the shl and shr modulos the rhs, so handle the `rhs >= 32u`
+                // cases using `select`. In order to handle the signed shr `lhs >> rhs` corrently,
+                // use `(lhs >> 31u) >> 1u` if `rhs >= 32u`.
+                body.Push(b.Decl(b.Let("shl_result", b.Call("select", b.Construct(T(ty)),
+                                                            b.Shl("v", vecN_u32(b.Expr("shl"))),
+                                                            b.LessThan("shl", 32_u)))));
+                body.Push(b.Return(b.Call(
+                    "select",
+                    b.Shr(b.Shr("shl_result", vecN_u32(b.Expr(31_u))), vecN_u32(b.Expr(1_u))),
+                    b.Shr("shl_result", vecN_u32(b.Expr("shr"))), b.LessThan("shr", 32_u))
+
+                                       ));
                 break;
             case Level::kClampParameters:
                 body.Push(b.Return(b.Call("extractBits", "v", "s", b.Sub("e", "s"))));
diff --git a/src/tint/transform/builtin_polyfill_test.cc b/src/tint/transform/builtin_polyfill_test.cc
index 87fd9d5..1fbea9c 100644
--- a/src/tint/transform/builtin_polyfill_test.cc
+++ b/src/tint/transform/builtin_polyfill_test.cc
@@ -1109,7 +1109,8 @@
   let e = min(32u, (s + count));
   let shl = (32u - e);
   let shr = (shl + s);
-  return ((v << shl) >> shr);
+  let shl_result = select(i32(), (v << shl), (shl < 32u));
+  return select(((shl_result >> 31u) >> 1u), (shl_result >> shr), (shr < 32u));
 }
 
 fn f() {
@@ -1137,7 +1138,8 @@
   let e = min(32u, (s + count));
   let shl = (32u - e);
   let shr = (shl + s);
-  return ((v << shl) >> shr);
+  let shl_result = select(u32(), (v << shl), (shl < 32u));
+  return select(((shl_result >> 31u) >> 1u), (shl_result >> shr), (shr < 32u));
 }
 
 fn f() {
@@ -1165,7 +1167,8 @@
   let e = min(32u, (s + count));
   let shl = (32u - e);
   let shr = (shl + s);
-  return ((v << vec3<u32>(shl)) >> vec3<u32>(shr));
+  let shl_result = select(vec3<i32>(), (v << vec3<u32>(shl)), (shl < 32u));
+  return select(((shl_result >> vec3<u32>(31u)) >> vec3<u32>(1u)), (shl_result >> vec3<u32>(shr)), (shr < 32u));
 }
 
 fn f() {
@@ -1193,7 +1196,8 @@
   let e = min(32u, (s + count));
   let shl = (32u - e);
   let shr = (shl + s);
-  return ((v << vec3<u32>(shl)) >> vec3<u32>(shr));
+  let shl_result = select(vec3<u32>(), (v << vec3<u32>(shl)), (shl < 32u));
+  return select(((shl_result >> vec3<u32>(31u)) >> vec3<u32>(1u)), (shl_result >> vec3<u32>(shr)), (shr < 32u));
 }
 
 fn f() {
diff --git a/src/tint/transform/canonicalize_entry_point_io.cc b/src/tint/transform/canonicalize_entry_point_io.cc
index 8a14fb7..b41529a 100644
--- a/src/tint/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/transform/canonicalize_entry_point_io.cc
@@ -359,10 +359,10 @@
                 continue;
             }
 
-            auto* member_ast = member->Declaration();
-            auto name = ctx.src->Symbols().NameFor(member_ast->symbol);
+            auto name = ctx.src->Symbols().NameFor(member->Name());
 
-            auto attributes = CloneShaderIOAttributes(member_ast->attributes, do_interpolate);
+            auto attributes =
+                CloneShaderIOAttributes(member->Declaration()->attributes, do_interpolate);
             auto* input_expr =
                 AddInput(name, member->Type(), member->Location(), std::move(attributes));
             inner_struct_values.Push(input_expr);
@@ -388,9 +388,9 @@
                     continue;
                 }
 
-                auto* member_ast = member->Declaration();
-                auto name = ctx.src->Symbols().NameFor(member_ast->symbol);
-                auto attributes = CloneShaderIOAttributes(member_ast->attributes, do_interpolate);
+                auto name = ctx.src->Symbols().NameFor(member->Name());
+                auto attributes =
+                    CloneShaderIOAttributes(member->Declaration()->attributes, do_interpolate);
 
                 // Extract the original structure member.
                 AddOutput(name, member->Type(), member->Location(), std::move(attributes),
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 944797a..671ebc4 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -645,8 +645,8 @@
                             utils::Vector<const ast::Statement*, 8> stmts;
                             for (auto* member : str->Members()) {
                                 auto* offset = b.Add("offset", u32(member->Offset()));
-                                auto* element = b.MemberAccessor(
-                                    "value", ctx.Clone(member->Declaration()->symbol));
+                                auto* element =
+                                    b.MemberAccessor("value", ctx.Clone(member->Name()));
                                 Symbol store =
                                     StoreFunc(buf_ty, member->Type()->UnwrapRef(), var_user);
                                 auto* call = b.Call(store, "buffer", offset, element);
diff --git a/src/tint/transform/decompose_strided_matrix_test.cc b/src/tint/transform/decompose_strided_matrix_test.cc
index bd202c9..e3ba4bb 100644
--- a/src/tint/transform/decompose_strided_matrix_test.cc
+++ b/src/tint/transform/decompose_strided_matrix_test.cc
@@ -90,6 +90,7 @@
 struct S {
   @size(16)
   padding : u32,
+  /* @offset(16) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
 
@@ -147,6 +148,7 @@
 struct S {
   @size(16)
   padding : u32,
+  /* @offset(16) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
 
@@ -199,6 +201,7 @@
 struct S {
   @size(16)
   padding : u32,
+  /* @offset(16u) */
   @stride(8) @internal(disable_validation__ignore_stride)
   m : mat2x2<f32>,
 }
@@ -253,6 +256,7 @@
 struct S {
   @size(8)
   padding : u32,
+  /* @offset(8) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
 
@@ -311,6 +315,7 @@
 struct S {
   @size(16)
   padding : u32,
+  /* @offset(16) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
 
@@ -365,6 +370,7 @@
 struct S {
   @size(8)
   padding : u32,
+  /* @offset(8) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
 
@@ -422,6 +428,7 @@
 struct S {
   @size(8)
   padding : u32,
+  /* @offset(8) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
 
@@ -487,6 +494,7 @@
 struct S {
   @size(8)
   padding : u32,
+  /* @offset(8) */
   m : @stride(32) array<vec2<f32>, 2u>,
 }
 
@@ -551,6 +559,7 @@
 struct S {
   @size(8)
   padding : u32,
+  /* @offset(8u) */
   @stride(32) @internal(disable_validation__ignore_stride)
   m : mat2x2<f32>,
 }
@@ -605,6 +614,7 @@
 struct S {
   @size(8)
   padding : u32,
+  /* @offset(8u) */
   @stride(32) @internal(disable_validation__ignore_stride)
   m : mat2x2<f32>,
 }
diff --git a/src/tint/transform/direct_variable_access.cc b/src/tint/transform/direct_variable_access.cc
new file mode 100644
index 0000000..d1c1339
--- /dev/null
+++ b/src/tint/transform/direct_variable_access.cc
@@ -0,0 +1,1216 @@
+// 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.
+
+#include "src/tint/transform/direct_variable_access.h"
+
+#include <algorithm>
+#include <string>
+#include <utility>
+
+#include "src/tint/ast/traverse_expressions.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/abstract_int.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/index_accessor_expression.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/module.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/transform/utils/hoist_to_decl_before.h"
+#include "src/tint/utils/reverse.h"
+#include "src/tint/utils/scoped_assignment.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::DirectVariableAccess);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::DirectVariableAccess::Config);
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace {
+
+/// AccessRoot describes the root of an AccessShape.
+struct AccessRoot {
+    /// The pointer-unwrapped type of the *transformed* variable.
+    /// This may be different for pointers in 'private' and 'function' address space, as the pointer
+    /// parameter type is to the *base object* instead of the input pointer type.
+    tint::sem::Type const* type = nullptr;
+    /// The originating module-scope variable ('private', 'storage', 'uniform', 'workgroup'),
+    /// function-scope variable ('function'), or pointer parameter in the source program.
+    tint::sem::Variable const* variable = nullptr;
+    /// The address space of the variable or pointer type.
+    tint::ast::AddressSpace address_space = tint::ast::AddressSpace::kUndefined;
+};
+
+/// Inequality operator for AccessRoot
+bool operator!=(const AccessRoot& a, const AccessRoot& b) {
+    return a.type != b.type || a.variable != b.variable;
+}
+
+/// DynamicIndex is used by DirectVariableAccess::State::AccessOp to indicate an array, matrix or
+/// vector index.
+struct DynamicIndex {
+    /// The index of the expression in DirectVariableAccess::State::AccessChain::dynamic_indices
+    size_t slot = 0;
+};
+
+/// Inequality operator for DynamicIndex
+bool operator!=(const DynamicIndex& a, const DynamicIndex& b) {
+    return a.slot != b.slot;
+}
+
+/// AccessOp describes a single access in an access chain.
+/// The access is one of:
+/// Symbol        - a struct member access.
+/// DynamicIndex  - a runtime index on an array, matrix column, or vector element.
+using AccessOp = std::variant<tint::Symbol, DynamicIndex>;
+
+/// A vector of AccessOp. Describes the static "path" from a root variable to an element
+/// within the variable. Array accessors index expressions are held externally to the
+/// AccessShape, so AccessShape will be considered equal even if the array, matrix or vector
+/// index values differ.
+///
+/// For example, consider the following:
+///
+/// ```
+///           struct A {
+///               x : array<i32, 8>,
+///               y : u32,
+///           };
+///           struct B {
+///               x : i32,
+///               y : array<A, 4>
+///           };
+///           var<workgroup> C : B;
+/// ```
+///
+/// The following AccessShape would describe the following:
+///
+/// +==============================+===============+=================================+
+/// | AccessShape                  | Type          |  Expression                     |
+/// +==============================+===============+=================================+
+/// | [ Variable 'C', Symbol 'x' ] | i32           |  C.x                            |
+/// +------------------------------+---------------+---------------------------------+
+/// | [ Variable 'C', Symbol 'y' ] | array<A, 4>   |  C.y                            |
+/// +------------------------------+---------------+---------------------------------+
+/// | [ Variable 'C', Symbol 'y',  | A             |  C.y[dyn_idx[0]]                |
+/// |   DynamicIndex ]             |               |                                 |
+/// +------------------------------+---------------+---------------------------------+
+/// | [ Variable 'C', Symbol 'y',  | array<i32, 8> |  C.y[dyn_idx[0]].x              |
+/// |   DynamicIndex, Symbol 'x' ] |               |                                 |
+/// +------------------------------+---------------+---------------------------------+
+/// | [ Variable 'C', Symbol 'y',  | i32           |  C.y[dyn_idx[0]].x[dyn_idx[1]]  |
+/// |   DynamicIndex, Symbol 'x',  |               |                                 |
+/// |   DynamicIndex ]             |               |                                 |
+/// +------------------------------+---------------+---------------------------------+
+/// | [ Variable 'C', Symbol 'y',  | u32           |  C.y[dyn_idx[0]].y              |
+/// |   DynamicIndex, Symbol 'y' ] |               |                                 |
+/// +------------------------------+---------------+---------------------------------+
+///
+/// Where: `dyn_idx` is the AccessChain::dynamic_indices.
+struct AccessShape {
+    // The originating variable.
+    AccessRoot root;
+    /// The chain of access ops.
+    tint::utils::Vector<AccessOp, 8> ops;
+
+    /// @returns the number of DynamicIndex operations in #ops.
+    uint32_t NumDynamicIndices() const {
+        uint32_t count = 0;
+        for (auto& op : ops) {
+            if (std::holds_alternative<DynamicIndex>(op)) {
+                count++;
+            }
+        }
+        return count;
+    }
+};
+
+/// Equality operator for AccessShape
+bool operator==(const AccessShape& a, const AccessShape& b) {
+    return !(a.root != b.root) && a.ops == b.ops;
+}
+
+/// Inequality operator for AccessShape
+bool operator!=(const AccessShape& a, const AccessShape& b) {
+    return !(a == b);
+}
+
+/// AccessChain describes a chain of access expressions originating from a variable.
+struct AccessChain : AccessShape {
+    /// The array accessor index expressions. This vector is indexed by the `DynamicIndex`s in
+    /// #indices.
+    tint::utils::Vector<const tint::sem::Expression*, 8> dynamic_indices;
+    /// If true, then this access chain is used as an argument to call a variant.
+    bool used_in_call = false;
+};
+
+}  // namespace
+
+namespace tint::utils {
+
+/// Hasher specialization for AccessRoot
+template <>
+struct Hasher<AccessRoot> {
+    /// The hash function for the AccessRoot
+    /// @param d the AccessRoot to hash
+    /// @return the hash for the given AccessRoot
+    size_t operator()(const AccessRoot& d) const { return utils::Hash(d.type, d.variable); }
+};
+
+/// Hasher specialization for DynamicIndex
+template <>
+struct Hasher<DynamicIndex> {
+    /// The hash function for the DynamicIndex
+    /// @param d the DynamicIndex to hash
+    /// @return the hash for the given DynamicIndex
+    size_t operator()(const DynamicIndex& d) const { return utils::Hash(d.slot); }
+};
+
+/// Hasher specialization for AccessShape
+template <>
+struct Hasher<AccessShape> {
+    /// The hash function for the AccessShape
+    /// @param s the AccessShape to hash
+    /// @return the hash for the given AccessShape
+    size_t operator()(const AccessShape& s) const { return utils::Hash(s.root, s.ops); }
+};
+
+}  // namespace tint::utils
+
+namespace tint::transform {
+
+/// The PIMPL state for the DirectVariableAccess transform
+struct DirectVariableAccess::State {
+    /// Constructor
+    /// @param src the source Program
+    /// @param options the transform options
+    State(const Program* src, const Options& options)
+        : ctx{&b, src, /* auto_clone_symbols */ true}, opts(options) {}
+
+    /// The main function for the transform.
+    /// @returns the ApplyResult
+    ApplyResult Run() {
+        if (!ctx.src->Sem().Module()->Extensions().Contains(
+                ast::Extension::kChromiumExperimentalFullPtrParameters)) {
+            // If the 'chromium_experimental_full_ptr_parameters' extension is not enabled, then
+            // there's nothing for this transform to do.
+            return SkipTransform;
+        }
+
+        // Stage 1:
+        // Walk all the expressions of the program, starting with the expression leaves.
+        // Whenever we find an identifier resolving to a var, pointer parameter or pointer let to
+        // another chain, start constructing an access chain. When chains are accessed, these chains
+        // are grown and moved up the expression tree. After this stage, we are left with all the
+        // expression access chains to variables that we may need to transform.
+        for (auto* node : ctx.src->ASTNodes().Objects()) {
+            if (auto* expr = sem.Get<sem::Expression>(node)) {
+                AppendAccessChain(expr);
+            }
+        }
+
+        // Stage 2:
+        // Walk the functions in dependency order, starting with the entry points.
+        // Construct the set of function 'variants' by examining the calls made by each function to
+        // their call target. Each variant holds a map of pointer parameter to access chains, and
+        // will have the pointer parameters replaced with an array of u32s, used to perform the
+        // pointer indexing in the variant.
+        // Function call pointer arguments are replaced with an array of these dynamic indices.
+        for (auto* decl : utils::Reverse(sem.Module()->DependencyOrderedDeclarations())) {
+            if (auto* fn = sem.Get<sem::Function>(decl)) {
+                auto* fn_info = FnInfoFor(fn);
+                ProcessFunction(fn, fn_info);
+                TransformFunction(fn, fn_info);
+            }
+        }
+
+        // Stage 3:
+        // Filter out access chains that do not need transforming.
+        // Ensure that chain dynamic index expressions are evaluated once at the correct place
+        ProcessAccessChains();
+
+        // Stage 4:
+        // Replace all the access chain expressions in all functions with reconstructed expression
+        // using the originating global variable, and any dynamic indices passed in to the function
+        // variant.
+        TransformAccessChainExpressions();
+
+        // Stage 5:
+        // Actually kick the clone.
+        CloneState state;
+        clone_state = &state;
+        ctx.Clone();
+        return Program(std::move(*ctx.dst));
+    }
+
+  private:
+    /// Holds symbols of the transformed pointer parameter.
+    /// If both symbols are valid, then #base_ptr and #indices are both program-unique symbols
+    /// derived from the original parameter name.
+    /// If only one symbol is valid, then this is the original parameter symbol.
+    struct PtrParamSymbols {
+        /// The symbol of the base pointer parameter.
+        Symbol base_ptr;
+        /// The symbol of the dynamic indicies parameter.
+        Symbol indices;
+    };
+
+    /// FnVariant describes a unique variant of a function, specialized by the AccessShape of the
+    /// pointer arguments - also known as the variant's "signature".
+    ///
+    /// To help understand what a variant is, consider the following WGSL:
+    ///
+    /// ```
+    /// fn F(a : ptr<storage, u32>, b : u32, c : ptr<storage, u32>) {
+    ///    return *a + b + *c;
+    /// }
+    ///
+    /// @group(0) @binding(0) var<storage> S0 : u32;
+    /// @group(0) @binding(0) var<storage> S1 : array<u32, 64>;
+    ///
+    /// fn x() {
+    ///    F(&S0, 0, &S0);       // (A)
+    ///    F(&S0, 0, &S0);       // (B)
+    ///    F(&S1[0], 1, &S0);    // (C)
+    ///    F(&S1[5], 2, &S0);    // (D)
+    ///    F(&S1[5], 3, &S1[3]); // (E)
+    ///    F(&S1[7], 4, &S1[2]); // (F)
+    /// }
+    /// ```
+    ///
+    /// Given the calls in x(), function F() will have 3 variants:
+    /// (1) F<S0,S0>                   - called by (A) and (B).
+    ///                                  Note that only 'uniform', 'storage' and 'workgroup' pointer
+    ///                                  parameters are considered for a variant signature, and so
+    ///                                  the argument for parameter 'b' is not included in the
+    ///                                  signature.
+    /// (2) F<S1[dyn_idx],S0>          - called by (C) and (D).
+    ///                                  Note that the array index value is external to the
+    ///                                  AccessShape, and so is not part of the variant signature.
+    /// (3) F<S1[dyn_idx],S1[dyn_idx]> - called by (E) and (F).
+    ///
+    /// Each variant of the function will be emitted as a separate function by the transform, and
+    /// would look something like:
+    ///
+    /// ```
+    /// // variant F<S0,S0> (1)
+    /// fn F_S0_S0(b : u32) {
+    ///    return S0 + b + S0;
+    /// }
+    ///
+    /// type S1_X = array<u32, 1>;
+    ///
+    /// // variant F<S1[dyn_idx],S0> (2)
+    /// fn F_S1_X_S0(a : S1_X, b : u32) {
+    ///    return S1[a[0]] + b + S0;
+    /// }
+    ///
+    /// // variant F<S1[dyn_idx],S1[dyn_idx]> (3)
+    /// fn F_S1_X_S1_X(a : S1_X, b : u32, c : S1_X) {
+    ///    return S1[a[0]] + b + S1[c[0]];
+    /// }
+    ///
+    /// @group(0) @binding(0) var<storage> S0 : u32;
+    /// @group(0) @binding(0) var<storage> S1 : array<u32, 64>;
+    ///
+    /// fn x() {
+    ///    F_S0_S0(0);                        // (A)
+    ///    F(&S0, 0, &S0);                    // (B)
+    ///    F_S1_X_S0(S1_X(0), 1);             // (C)
+    ///    F_S1_X_S0(S1_X(5), 2);             // (D)
+    ///    F_S1_X_S1_X(S1_X(5), 3, S1_X(3));  // (E)
+    ///    F_S1_X_S1_X(S1_X(7), 4, S1_X(2));  // (F)
+    /// }
+    /// ```
+    struct FnVariant {
+        /// The signature of the variant is a map of each of the function's 'uniform', 'storage' and
+        /// 'workgroup' pointer parameters to the caller's AccessShape.
+        using Signature = utils::Hashmap<const sem::Parameter*, AccessShape, 4>;
+
+        /// The unique name of the variant.
+        /// The symbol is in the `ctx.dst` program namespace.
+        Symbol name;
+
+        /// A map of direct calls made by this variant to the name of other function variants.
+        utils::Hashmap<const sem::Call*, Symbol, 4> calls;
+
+        /// A map of input program parameter to output parameter symbols.
+        utils::Hashmap<const sem::Parameter*, PtrParamSymbols, 4> ptr_param_symbols;
+
+        /// The declaration order of the variant, in relation to other variants of the same
+        /// function. Used to ensure deterministic ordering of the transform, as map iteration is
+        /// not deterministic between compilers.
+        size_t order = 0;
+    };
+
+    /// FnInfo holds information about a function in the input program.
+    struct FnInfo {
+        /// A map of variant signature to the variant data.
+        utils::Hashmap<FnVariant::Signature, FnVariant, 8> variants;
+        /// A map of expressions that have been hoisted to a 'let' declaration in the function.
+        utils::Hashmap<const sem::Expression*, Symbol, 8> hoisted_exprs;
+
+        /// @returns the variants of the function in a deterministically ordered vector.
+        utils::Vector<std::pair<const FnVariant::Signature*, FnVariant*>, 8> SortedVariants() {
+            utils::Vector<std::pair<const FnVariant::Signature*, FnVariant*>, 8> out;
+            out.Reserve(variants.Count());
+            for (auto it : variants) {
+                out.Push({&it.key, &it.value});
+            }
+            out.Sort([&](auto& va, auto& vb) { return va.second->order < vb.second->order; });
+            return out;
+        }
+    };
+
+    /// The program builder
+    ProgramBuilder b;
+    /// The clone context
+    CloneContext ctx;
+    /// The transform options
+    const Options& opts;
+    /// Alias to the semantic info in ctx.src
+    const sem::Info& sem = ctx.src->Sem();
+    /// Alias to the symbols in ctx.src
+    const SymbolTable& sym = ctx.src->Symbols();
+    /// Map of semantic function to the function info
+    utils::Hashmap<const sem::Function*, FnInfo*, 8> fns;
+    /// Map of AccessShape to the name of a type alias for the an array<u32, N> used for the
+    /// dynamic indices of an access chain, passed down as the transformed type of a variant's
+    /// pointer parameter.
+    utils::Hashmap<AccessShape, Symbol, 8> dynamic_index_array_aliases;
+    /// Map of semantic expression to AccessChain
+    utils::Hashmap<const sem::Expression*, AccessChain*, 32> access_chains;
+    /// Allocator for FnInfo
+    utils::BlockAllocator<FnInfo> fn_info_allocator;
+    /// Allocator for AccessChain
+    utils::BlockAllocator<AccessChain> access_chain_allocator;
+    /// Helper used for hoisting expressions to lets
+    HoistToDeclBefore hoist{ctx};
+    /// Map of string to unique symbol (no collisions in output program).
+    utils::Hashmap<std::string, Symbol, 8> unique_symbols;
+
+    /// CloneState holds pointers to the current function, variant and variant's parameters.
+    struct CloneState {
+        /// The current function being cloned
+        FnInfo* current_function = nullptr;
+        /// The current function variant being built
+        FnVariant* current_variant = nullptr;
+        /// The signature of the current function variant being built
+        const FnVariant::Signature* current_variant_sig = nullptr;
+    };
+
+    /// The clone state.
+    /// Only valid during the lifetime of the CloneContext::Clone().
+    CloneState* clone_state = nullptr;
+
+    /// AppendAccessChain creates or extends an existing AccessChain for the given expression,
+    /// modifying the #access_chains map.
+    void AppendAccessChain(const sem::Expression* expr) {
+        // take_chain moves the AccessChain from the expression `from` to the expression `expr`.
+        // Returns nullptr if `from` did not hold an access chain.
+        auto take_chain = [&](const sem::Expression* from) -> AccessChain* {
+            if (auto* chain = AccessChainFor(from)) {
+                access_chains.Remove(from);
+                access_chains.Add(expr, chain);
+                return chain;
+            }
+            return nullptr;
+        };
+
+        Switch(
+            expr,
+            [&](const sem::VariableUser* user) {
+                // Expression resolves to a variable.
+                auto* variable = user->Variable();
+
+                auto create_new_chain = [&] {
+                    auto* chain = access_chain_allocator.Create();
+                    chain->root.variable = variable;
+                    chain->root.type = variable->Type();
+                    chain->root.address_space = variable->AddressSpace();
+                    if (auto* ptr = chain->root.type->As<sem::Pointer>()) {
+                        chain->root.address_space = ptr->AddressSpace();
+                    }
+                    access_chains.Add(expr, chain);
+                };
+
+                Switch(
+                    variable->Declaration(),
+                    [&](const ast::Var*) {
+                        if (variable->AddressSpace() != ast::AddressSpace::kHandle) {
+                            // Start a new access chain for the non-handle 'var' access
+                            create_new_chain();
+                        }
+                    },
+                    [&](const ast::Parameter*) {
+                        if (variable->Type()->Is<sem::Pointer>()) {
+                            // Start a new access chain for the pointer parameter access
+                            create_new_chain();
+                        }
+                    },
+                    [&](const ast::Let*) {
+                        if (variable->Type()->Is<sem::Pointer>()) {
+                            // variable is a pointer-let.
+                            auto* init = sem.Get(variable->Declaration()->initializer);
+                            // Note: We do not use take_chain() here, as we need to preserve the
+                            // AccessChain on the let's initializer, as the let needs its
+                            // initializer updated, and the let may be used multiple times. Instead
+                            // we copy the let's AccessChain into a a new AccessChain.
+                            if (auto* init_chain = AccessChainFor(init)) {
+                                access_chains.Add(expr, access_chain_allocator.Create(*init_chain));
+                            }
+                        }
+                    });
+            },
+            [&](const sem::StructMemberAccess* a) {
+                // Structure member access.
+                // Append the Symbol of the member name to the chain, and move the chain to the
+                // member access expression.
+                if (auto* chain = take_chain(a->Object())) {
+                    chain->ops.Push(a->Member()->Name());
+                }
+            },
+            [&](const sem::IndexAccessorExpression* a) {
+                // Array, matrix or vector index.
+                // Store the index expression into AccessChain::dynamic_indices, append a
+                // DynamicIndex to the chain, and move the chain to the index accessor expression.
+                if (auto* chain = take_chain(a->Object())) {
+                    chain->ops.Push(DynamicIndex{chain->dynamic_indices.Length()});
+                    chain->dynamic_indices.Push(a->Index());
+                }
+            },
+            [&](const sem::Expression* e) {
+                if (auto* unary = e->Declaration()->As<ast::UnaryOpExpression>()) {
+                    // Unary op.
+                    // If this is a '&' or '*', simply move the chain to the unary op expression.
+                    if (unary->op == ast::UnaryOp::kAddressOf ||
+                        unary->op == ast::UnaryOp::kIndirection) {
+                        take_chain(sem.Get(unary->expr));
+                    }
+                }
+            });
+    }
+
+    /// MaybeHoistDynamicIndices examines the AccessChain::dynamic_indices member of @p chain,
+    /// hoisting all expressions to their own uniquely named 'let' if none of the following are
+    /// true:
+    /// 1. The index expression is a constant value.
+    /// 2. The index expression's statement is the same as @p usage.
+    /// 3. The index expression is an identifier resolving to a 'let', 'const' or parameter, AND
+    ///    that identifier resolves to the same variable at @p usage.
+    ///
+    /// A dynamic index will only be hoisted once. The hoisting applies to all variants of the
+    /// function that holds the dynamic index expression.
+    void MaybeHoistDynamicIndices(AccessChain* chain, const sem::Statement* usage) {
+        for (auto& idx : chain->dynamic_indices) {
+            if (idx->ConstantValue()) {
+                // Dynamic index is constant.
+                continue;  // Hoisting not required.
+            }
+
+            if (idx->Stmt() == usage) {
+                // The index expression is owned by the statement of usage.
+                continue;  // Hoisting not required
+            }
+
+            if (auto* idx_variable_user = idx->UnwrapMaterialize()->As<sem::VariableUser>()) {
+                auto* idx_variable = idx_variable_user->Variable();
+                if (idx_variable->Declaration()->IsAnyOf<ast::Let, ast::Parameter>()) {
+                    // Dynamic index is an immutable variable
+                    continue;  // Hoisting not required.
+                }
+            }
+
+            // The dynamic index needs to be hoisted (if it hasn't been already).
+            auto fn = FnInfoFor(idx->Stmt()->Function());
+            fn->hoisted_exprs.GetOrCreate(idx, [=] {
+                // Create a name for the new 'let'
+                auto name = b.Symbols().New("ptr_index_save");
+                // Insert a new 'let' just above the dynamic index statement.
+                hoist.InsertBefore(idx->Stmt(), [this, idx, name] {
+                    return b.Decl(b.Let(name, ctx.CloneWithoutTransform(idx->Declaration())));
+                });
+                return name;
+            });
+        }
+    }
+
+    /// BuildDynamicIndex builds the AST expression node for the dynamic index expression used in an
+    /// AccessChain. This is similar to just cloning the expression, but BuildDynamicIndex()
+    /// also:
+    /// * Collapses constant value index expressions down to the computed value. This acts as an
+    ///   constant folding optimization and reduces noise from the transform.
+    /// * Casts the resulting expression to a u32 if @p cast_to_u32 is true, and the expression type
+    ///   isn't implicitly usable as a u32. This is to help feed the expression into a
+    ///   `array<u32, N>` argument passed to a callee variant function.
+    const ast::Expression* BuildDynamicIndex(const sem::Expression* idx, bool cast_to_u32) {
+        if (auto* val = idx->ConstantValue()) {
+            // Expression evaluated to a constant value. Just emit that constant.
+            return b.Expr(val->As<AInt>());
+        }
+
+        // Expression is not a constant, clone the expression.
+        // Note: If the dynamic index expression was hoisted to a let, then cloning will return an
+        // identifier expression to the hoisted let.
+        auto* expr = ctx.Clone(idx->Declaration());
+
+        if (cast_to_u32) {
+            // The index may be fed to a dynamic index array<u32, N> argument, so the index
+            // expression may need casting to u32.
+            if (!idx->UnwrapMaterialize()
+                     ->Type()
+                     ->UnwrapRef()
+                     ->IsAnyOf<sem::U32, sem::AbstractInt>()) {
+                expr = b.Construct(b.ty.u32(), expr);
+            }
+        }
+
+        return expr;
+    }
+
+    /// ProcessFunction scans the direct calls made by the function @p fn, adding new variants to
+    /// the callee functions and transforming the call expression to pass dynamic indices instead of
+    /// true pointers.
+    /// If the function @p fn has pointer parameters that must be transformed to a caller variant,
+    /// and the function is not called, then the function is dropped from the output of the
+    /// transform, as it cannot be generated.
+    /// @note ProcessFunction must be called in dependency order for the program, starting with the
+    /// entry points.
+    void ProcessFunction(const sem::Function* fn, FnInfo* fn_info) {
+        if (fn_info->variants.IsEmpty()) {
+            // Function has no variants pre-generated by callers.
+            if (MustBeCalled(fn)) {
+                // Drop the function, as it wasn't called and cannot be generated.
+                ctx.Remove(ctx.src->AST().GlobalDeclarations(), fn->Declaration());
+                return;
+            }
+
+            // Function was not called. Create a single variant with an empty signature.
+            FnVariant variant;
+            variant.name = ctx.Clone(fn->Declaration()->symbol);
+            variant.order = 0;  // Unaltered comes first.
+            fn_info->variants.Add(FnVariant::Signature{}, std::move(variant));
+        }
+
+        // Process each of the direct calls made by this function.
+        for (auto* call : fn->DirectCalls()) {
+            ProcessCall(fn_info, call);
+        }
+    }
+
+    /// ProcessCall creates new variants of the callee function by permuting the call for each of
+    /// the variants of @p caller. ProcessCall also registers the clone callback to transform the
+    /// call expression to pass dynamic indices instead of true pointers.
+    void ProcessCall(FnInfo* caller, const sem::Call* call) {
+        auto* target = call->Target()->As<sem::Function>();
+        if (!target) {
+            // Call target is not a user-declared function.
+            return;  // Not interested in this call.
+        }
+
+        if (!HasPointerParameter(target)) {
+            return;  // Not interested in this call.
+        }
+
+        bool call_needs_transforming = false;
+
+        // Build the call target function variant for each variant of the caller.
+        for (auto caller_variant_it : caller->SortedVariants()) {
+            auto& caller_signature = *caller_variant_it.first;
+            auto& caller_variant = *caller_variant_it.second;
+
+            // Build the target variant's signature.
+            FnVariant::Signature target_signature;
+            for (size_t i = 0; i < call->Arguments().Length(); i++) {
+                const auto* arg = call->Arguments()[i];
+                const auto* param = target->Parameters()[i];
+                const auto* param_ty = param->Type()->As<sem::Pointer>();
+                if (!param_ty) {
+                    continue;  // Parameter type is not a pointer.
+                }
+
+                // Fetch the access chain for the argument.
+                auto* arg_chain = AccessChainFor(arg);
+                if (!arg_chain) {
+                    continue;  // Argument does not have an access chain
+                }
+
+                // Construct the absolute AccessShape by considering the AccessShape of the caller
+                // variant's argument. This will propagate back through pointer parameters, to the
+                // outermost caller.
+                auto absolute = AbsoluteAccessShape(caller_signature, *arg_chain);
+
+                // If the address space of the root variable of the access chain does not require
+                // transformation, then there's nothing to do.
+                if (!AddressSpaceRequiresTransform(absolute.root.address_space)) {
+                    continue;
+                }
+
+                // Record that this chain was used in a function call.
+                // This preserves the chain during the access chain filtering stage.
+                arg_chain->used_in_call = true;
+
+                if (IsPrivateOrFunction(absolute.root.address_space)) {
+                    // Pointers in 'private' and 'function' address spaces need to be passed by
+                    // pointer argument.
+                    absolute.root.variable = param;
+                }
+
+                // Add the parameter's absolute AccessShape to the target's signature.
+                target_signature.Add(param, std::move(absolute));
+            }
+
+            // Construct a new FnVariant if this is the first caller of the target signature
+            auto* target_info = FnInfoFor(target);
+            auto& target_variant = target_info->variants.GetOrCreate(target_signature, [&] {
+                if (target_signature.IsEmpty()) {
+                    // Call target does not require any argument changes.
+                    FnVariant variant;
+                    variant.name = ctx.Clone(target->Declaration()->symbol);
+                    variant.order = 0;  // Unaltered comes first.
+                    return variant;
+                }
+
+                // Build an appropriate variant function name.
+                // This is derived from the original function name and the pointer parameter
+                // chains.
+                std::stringstream ss;
+                ss << ctx.src->Symbols().NameFor(target->Declaration()->symbol);
+                for (auto* param : target->Parameters()) {
+                    if (auto indices = target_signature.Find(param)) {
+                        ss << "_" << AccessShapeName(*indices);
+                    }
+                }
+
+                // Build the pointer parameter symbols.
+                utils::Hashmap<const sem::Parameter*, PtrParamSymbols, 4> ptr_param_symbols;
+                for (auto param_it : target_signature) {
+                    auto* param = param_it.key;
+                    auto& shape = param_it.value;
+
+                    // Parameter needs replacing with either zero, one or two parameters:
+                    // If the parameter is in the 'private' or 'function' address space, then the
+                    // originating pointer is always passed down. This always comes first.
+                    // If the access chain has dynamic indices, then we create an array<u32, N>
+                    // parameter to hold the dynamic indices.
+                    bool requires_base_ptr_param = IsPrivateOrFunction(shape.root.address_space);
+                    bool requires_indices_param = shape.NumDynamicIndices() > 0;
+
+                    PtrParamSymbols symbols;
+                    if (requires_base_ptr_param && requires_indices_param) {
+                        auto original_name = param->Declaration()->symbol;
+                        symbols.base_ptr = UniqueSymbolWithSuffix(original_name, "_base");
+                        symbols.indices = UniqueSymbolWithSuffix(original_name, "_indices");
+                    } else if (requires_base_ptr_param) {
+                        symbols.base_ptr = ctx.Clone(param->Declaration()->symbol);
+                    } else if (requires_indices_param) {
+                        symbols.indices = ctx.Clone(param->Declaration()->symbol);
+                    }
+
+                    // Remember this base pointer name.
+                    ptr_param_symbols.Add(param, symbols);
+                }
+
+                // Build the variant.
+                FnVariant variant;
+                variant.name = b.Symbols().New(ss.str());
+                variant.order = target_info->variants.Count() + 1;
+                variant.ptr_param_symbols = std::move(ptr_param_symbols);
+                return variant;
+            });
+
+            // Record the call made by caller variant to the target variant.
+            caller_variant.calls.Add(call, target_variant.name);
+            if (!target_signature.IsEmpty()) {
+                // The call expression will need transforming for at least one caller variant.
+                call_needs_transforming = true;
+            }
+        }
+
+        if (call_needs_transforming) {
+            // Register the clone callback to correctly transform the call expression into the
+            // appropriate variant calls.
+            TransformCall(call);
+        }
+    }
+
+    /// @returns true if the address space @p address_space requires transforming given the
+    /// transform's options.
+    bool AddressSpaceRequiresTransform(ast::AddressSpace address_space) const {
+        switch (address_space) {
+            case ast::AddressSpace::kUniform:
+            case ast::AddressSpace::kStorage:
+            case ast::AddressSpace::kWorkgroup:
+                return true;
+            case ast::AddressSpace::kPrivate:
+                return opts.transform_private;
+            case ast::AddressSpace::kFunction:
+                return opts.transform_function;
+            default:
+                return false;
+        }
+    }
+
+    /// @returns the AccessChain for the expression @p expr, or nullptr if the expression does
+    /// not hold an access chain.
+    AccessChain* AccessChainFor(const sem::Expression* expr) const {
+        if (auto chain = access_chains.Find(expr)) {
+            return *chain;
+        }
+        return nullptr;
+    }
+
+    /// @returns the absolute AccessShape for @p indices, by replacing the originating pointer
+    /// parameter with the AccessChain of variant's signature.
+    AccessShape AbsoluteAccessShape(const FnVariant::Signature& signature,
+                                    const AccessShape& shape) const {
+        if (auto* root_param = shape.root.variable->As<sem::Parameter>()) {
+            if (auto incoming_chain = signature.Find(root_param)) {
+                // Access chain originates from a parameter, which will be transformed into an array
+                // of dynamic indices. Concatenate the signature's AccessShape for the parameter
+                // to the chain's indices, skipping over the chain's initial parameter index.
+                auto absolute = *incoming_chain;
+                for (auto& op : shape.ops) {
+                    absolute.ops.Push(op);
+                }
+                return absolute;
+            }
+        }
+
+        // Chain does not originate from a parameter, so is already absolute.
+        return shape;
+    }
+
+    /// TransformFunction registers the clone callback to transform the function @p fn into the
+    /// (potentially multiple) function's variants. TransformFunction will assign the current
+    /// function and variant to #clone_state, which can be used by the other clone callbacks.
+    void TransformFunction(const sem::Function* fn, FnInfo* fn_info) {
+        // Register a custom handler for the specific function
+        ctx.Replace(fn->Declaration(), [this, fn, fn_info] {
+            // For the scope of this lambda, assign current_function to fn_info.
+            TINT_SCOPED_ASSIGNMENT(clone_state->current_function, fn_info);
+
+            // This callback expects a single function returned. As we're generating potentially
+            // many variant functions, keep a record of the last created variant, and explicitly add
+            // this to the module if it isn't the last. We'll return the last created variant,
+            // taking the place of the original function.
+            const ast::Function* pending_variant = nullptr;
+
+            // For each variant of fn...
+            for (auto variant_it : fn_info->SortedVariants()) {
+                if (pending_variant) {
+                    b.AST().AddFunction(pending_variant);
+                }
+
+                auto& variant_sig = *variant_it.first;
+                auto& variant = *variant_it.second;
+
+                // For the rest of this scope, assign the current variant and variant signature.
+                TINT_SCOPED_ASSIGNMENT(clone_state->current_variant_sig, &variant_sig);
+                TINT_SCOPED_ASSIGNMENT(clone_state->current_variant, &variant);
+
+                // Build the variant's parameters.
+                // Pointer parameters in the 'uniform', 'storage' or 'workgroup' address space are
+                // either replaced with an array of dynamic indices, or are dropped (if there are no
+                // dynamic indices).
+                utils::Vector<const ast::Parameter*, 8> params;
+                for (auto* param : fn->Parameters()) {
+                    if (auto incoming_shape = variant_sig.Find(param)) {
+                        auto& symbols = *variant.ptr_param_symbols.Find(param);
+                        if (symbols.base_ptr.IsValid()) {
+                            auto* base_ptr_ty =
+                                b.ty.pointer(CreateASTTypeFor(ctx, incoming_shape->root.type),
+                                             incoming_shape->root.address_space);
+                            params.Push(b.Param(symbols.base_ptr, base_ptr_ty));
+                        }
+                        if (symbols.indices.IsValid()) {
+                            // Variant has dynamic indices for this variant, replace it.
+                            auto* dyn_idx_arr_type = DynamicIndexArrayType(*incoming_shape);
+                            params.Push(b.Param(symbols.indices, dyn_idx_arr_type));
+                        }
+                    } else {
+                        // Just a regular parameter. Just clone the original parameter.
+                        params.Push(ctx.Clone(param->Declaration()));
+                    }
+                }
+
+                // Build the variant by cloning the source function. The other clone callbacks will
+                // use clone_state->current_variant and clone_state->current_variant_sig to produce
+                // the variant.
+                auto* ret_ty = ctx.Clone(fn->Declaration()->return_type);
+                auto body = ctx.Clone(fn->Declaration()->body);
+                auto attrs = ctx.Clone(fn->Declaration()->attributes);
+                auto ret_attrs = ctx.Clone(fn->Declaration()->return_type_attributes);
+                pending_variant =
+                    b.create<ast::Function>(variant.name, std::move(params), ret_ty, body,
+                                            std::move(attrs), std::move(ret_attrs));
+            }
+
+            return pending_variant;
+        });
+    }
+
+    /// TransformCall registers the clone callback to transform the call expression @p call to call
+    /// the correct target variant, and to replace pointers arguments with an array of dynamic
+    /// indices.
+    void TransformCall(const sem::Call* call) {
+        // Register a custom handler for the specific call expression
+        ctx.Replace(call->Declaration(), [this, call]() {
+            auto target_variant = clone_state->current_variant->calls.Find(call);
+            if (!target_variant) {
+                // The current variant does not need to transform this call.
+                return ctx.CloneWithoutTransform(call->Declaration());
+            }
+
+            // Build the new call expressions's arguments.
+            utils::Vector<const ast::Expression*, 8> new_args;
+            for (size_t arg_idx = 0; arg_idx < call->Arguments().Length(); arg_idx++) {
+                auto* arg = call->Arguments()[arg_idx];
+                auto* param = call->Target()->Parameters()[arg_idx];
+                auto* param_ty = param->Type()->As<sem::Pointer>();
+                if (!param_ty) {
+                    // Parameter is not a pointer.
+                    // Just clone the unaltered argument.
+                    new_args.Push(ctx.Clone(arg->Declaration()));
+                    continue;  // Parameter is not a pointer
+                }
+
+                auto* chain = AccessChainFor(arg);
+                if (!chain) {
+                    // No access chain means the argument is not a pointer that needs transforming.
+                    // Just clone the unaltered argument.
+                    new_args.Push(ctx.Clone(arg->Declaration()));
+                    continue;
+                }
+
+                // Construct the absolute AccessShape by considering the AccessShape of the caller
+                // variant's argument. This will propagate back through pointer parameters, to the
+                // outermost caller.
+                auto full_indices = AbsoluteAccessShape(*clone_state->current_variant_sig, *chain);
+
+                // If the parameter is a pointer in the 'private' or 'function' address space, then
+                // we need to pass an additional pointer argument to the base object.
+                if (IsPrivateOrFunction(param_ty->AddressSpace())) {
+                    auto* root_expr = BuildAccessRootExpr(chain->root, /* deref */ false);
+                    if (!chain->root.variable->Is<sem::Parameter>()) {
+                        root_expr = b.AddressOf(root_expr);
+                    }
+                    new_args.Push(root_expr);
+                }
+
+                // Get or create the dynamic indices array.
+                if (auto* dyn_idx_arr_ty = DynamicIndexArrayType(full_indices)) {
+                    // Build an array of dynamic indices to pass as the replacement for the pointer.
+                    utils::Vector<const ast::Expression*, 8> dyn_idx_args;
+                    if (auto* root_param = chain->root.variable->As<sem::Parameter>()) {
+                        // Access chain originates from a pointer parameter.
+                        if (auto incoming_chain =
+                                clone_state->current_variant_sig->Find(root_param)) {
+                            auto indices =
+                                clone_state->current_variant->ptr_param_symbols.Find(root_param)
+                                    ->indices;
+
+                            // This pointer parameter will have been replaced with a array<u32, N>
+                            // holding the variant's dynamic indices for the pointer. Unpack these
+                            // directly into the array constructor's arguments.
+                            auto N = incoming_chain->NumDynamicIndices();
+                            for (uint32_t i = 0; i < N; i++) {
+                                dyn_idx_args.Push(b.IndexAccessor(indices, u32(i)));
+                            }
+                        }
+                    }
+                    // Pass the dynamic indices of the access chain into the array constructor.
+                    for (auto& dyn_idx : chain->dynamic_indices) {
+                        dyn_idx_args.Push(BuildDynamicIndex(dyn_idx, /* cast_to_u32 */ true));
+                    }
+                    // Construct the dynamic index array, and push as an argument.
+                    new_args.Push(b.Construct(dyn_idx_arr_ty, std::move(dyn_idx_args)));
+                }
+            }
+
+            // Make the call to the target's variant.
+            return b.Call(*target_variant, std::move(new_args));
+        });
+    }
+
+    /// ProcessAccessChains performs the following:
+    /// * Removes all AccessChains from expressions that are not either used as a pointer argument
+    ///   in a call, or originates from a pointer parameter.
+    /// * Hoists the dynamic index expressions of AccessChains to 'let' statements, to prevent
+    ///   multiple evaluation of the expressions, and avoid expressions resolving to different
+    ///   variables based on lexical scope.
+    void ProcessAccessChains() {
+        auto chain_exprs = access_chains.Keys();
+        chain_exprs.Sort([](const auto& expr_a, const auto& expr_b) {
+            return expr_a->Declaration()->node_id.value < expr_b->Declaration()->node_id.value;
+        });
+
+        for (auto* expr : chain_exprs) {
+            auto* chain = *access_chains.Get(expr);
+            if (!chain->used_in_call && !chain->root.variable->Is<sem::Parameter>()) {
+                // Chain was not used in a function call, and does not originate from a
+                // parameter. This chain does not need transforming. Drop it.
+                access_chains.Remove(expr);
+                continue;
+            }
+
+            // Chain requires transforming.
+
+            // We need to be careful that the chain does not use expressions with side-effects which
+            // cannot be repeatedly evaluated. In this situation we can hoist the dynamic index
+            // expressions to their own uniquely named lets (if required).
+            MaybeHoistDynamicIndices(chain, expr->Stmt());
+        }
+    }
+
+    /// TransformAccessChainExpressions registers the clone callback to:
+    /// * Transform all expressions that have an AccessChain (which aren't arguments to function
+    ///   calls, these are handled by TransformCall()), into the equivalent expression using a
+    ///   module-scope variable.
+    /// * Replace expressions that have been hoisted to a let, with an identifier expression to that
+    ///   let.
+    void TransformAccessChainExpressions() {
+        // Register a custom handler for all non-function call expressions
+        ctx.ReplaceAll([this](const ast::Expression* ast_expr) -> const ast::Expression* {
+            if (!clone_state->current_variant) {
+                // Expression does not belong to a function variant.
+                return nullptr;  // Just clone the expression.
+            }
+
+            auto* expr = sem.Get<sem::Expression>(ast_expr);
+            if (!expr) {
+                // No semantic node for the expression.
+                return nullptr;  // Just clone the expression.
+            }
+
+            // If the expression has been hoisted to a 'let', then replace the expression with an
+            // identifier to the hoisted let.
+            if (auto hoisted = clone_state->current_function->hoisted_exprs.Find(expr)) {
+                return b.Expr(*hoisted);
+            }
+
+            auto* chain = AccessChainFor(expr);
+            if (!chain) {
+                // The expression does not have an AccessChain.
+                return nullptr;  // Just clone the expression.
+            }
+
+            auto* root_param = chain->root.variable->As<sem::Parameter>();
+            if (!root_param) {
+                // The expression has an access chain, but does not originate with a pointer
+                // parameter. We don't need to change anything here.
+                return nullptr;  // Just clone the expression.
+            }
+
+            auto incoming_shape = clone_state->current_variant_sig->Find(root_param);
+            if (!incoming_shape) {
+                // The root parameter of the access chain is not part of the variant's signature.
+                return nullptr;  // Just clone the expression.
+            }
+
+            // Expression holds an access chain to a pointer parameter that needs transforming.
+            // Reconstruct the expression using the variant's incoming shape.
+
+            auto* chain_expr = BuildAccessRootExpr(incoming_shape->root, /* deref */ true);
+
+            // Chain starts with a pointer parameter.
+            // Replace this with the variant's incoming shape. This will bring the expression up to
+            // the incoming pointer.
+            auto indices =
+                clone_state->current_variant->ptr_param_symbols.Find(root_param)->indices;
+            for (auto param_access : incoming_shape->ops) {
+                chain_expr = BuildAccessExpr(chain_expr, param_access, [&](size_t i) {
+                    return b.IndexAccessor(indices, AInt(i));
+                });
+            }
+
+            // Now build the expression chain within the function.
+
+            // For each access in the chain (excluding the pointer parameter)...
+            for (auto& op : chain->ops) {
+                chain_expr = BuildAccessExpr(chain_expr, op, [&](size_t i) {
+                    return BuildDynamicIndex(chain->dynamic_indices[i], false);
+                });
+            }
+
+            // BuildAccessExpr() always returns a non-pointer.
+            // If the expression we're replacing is a pointer, take the address.
+            if (expr->Type()->Is<sem::Pointer>()) {
+                chain_expr = b.AddressOf(chain_expr);
+            }
+
+            return chain_expr;
+        });
+    }
+
+    /// @returns the FnInfo for the given function, constructing a new FnInfo if @p fn doesn't
+    /// already have one.
+    FnInfo* FnInfoFor(const sem::Function* fn) {
+        return fns.GetOrCreate(fn, [this] { return fn_info_allocator.Create(); });
+    }
+
+    /// @returns the type alias used to hold the dynamic indices for @p shape, declaring a new alias
+    /// if this is the first call for the given shape.
+    const ast::TypeName* DynamicIndexArrayType(const AccessShape& shape) {
+        auto name = dynamic_index_array_aliases.GetOrCreate(shape, [&] {
+            // Count the number of dynamic indices
+            uint32_t num_dyn_indices = shape.NumDynamicIndices();
+            if (num_dyn_indices == 0) {
+                return Symbol{};
+            }
+            auto symbol = b.Symbols().New(AccessShapeName(shape));
+            b.Alias(symbol, b.ty.array(b.ty.u32(), u32(num_dyn_indices)));
+            return symbol;
+        });
+        return name.IsValid() ? b.ty.type_name(name) : nullptr;
+    }
+
+    /// @returns a name describing the given shape
+    std::string AccessShapeName(const AccessShape& shape) {
+        std::stringstream ss;
+
+        if (IsPrivateOrFunction(shape.root.address_space)) {
+            ss << "F";
+        } else {
+            ss << ctx.src->Symbols().NameFor(shape.root.variable->Declaration()->symbol);
+        }
+
+        for (auto& op : shape.ops) {
+            ss << "_";
+
+            if (std::holds_alternative<DynamicIndex>(op)) {
+                /// The op uses a dynamic (runtime-expression) index.
+                ss << "X";
+                continue;
+            }
+
+            if (auto* member = std::get_if<Symbol>(&op)) {
+                ss << sym.NameFor(*member);
+                continue;
+            }
+
+            TINT_ICE(Transform, b.Diagnostics()) << "unhandled variant for access chain";
+            break;
+        }
+        return ss.str();
+    }
+
+    /// Builds an expresion to the root of an access, returning the new expression.
+    /// @param root the AccessRoot
+    /// @param deref if true, the returned expression will always be a reference type.
+    const ast::Expression* BuildAccessRootExpr(const AccessRoot& root, bool deref) {
+        if (auto* param = root.variable->As<sem::Parameter>()) {
+            if (auto symbols = clone_state->current_variant->ptr_param_symbols.Find(param)) {
+                if (deref) {
+                    return b.Deref(b.Expr(symbols->base_ptr));
+                }
+                return b.Expr(symbols->base_ptr);
+            }
+        }
+
+        const ast::Expression* expr = b.Expr(ctx.Clone(root.variable->Declaration()->symbol));
+        if (deref) {
+            if (root.variable->Type()->Is<sem::Pointer>()) {
+                expr = b.Deref(expr);
+            }
+        }
+        return expr;
+    }
+
+    /// Builds a single access in an access chain, returning the new expression.
+    /// The returned expression will always be of a reference type.
+    /// @param expr the input expression
+    /// @param access the access to perform on the current expression
+    /// @param dynamic_index a function that obtains the i'th dynamic index
+    const ast::Expression* BuildAccessExpr(
+        const ast::Expression* expr,
+        const AccessOp& access,
+        std::function<const ast::Expression*(size_t)> dynamic_index) {
+        if (auto* dyn_idx = std::get_if<DynamicIndex>(&access)) {
+            /// The access uses a dynamic (runtime-expression) index.
+            auto* idx = dynamic_index(dyn_idx->slot);
+            return b.IndexAccessor(expr, idx);
+        }
+
+        if (auto* member = std::get_if<Symbol>(&access)) {
+            /// The access is a member access.
+            return b.MemberAccessor(expr, ctx.Clone(*member));
+        }
+
+        TINT_ICE(Transform, b.Diagnostics()) << "unhandled variant type for access chain";
+        return nullptr;
+    }
+
+    /// @returns a new Symbol starting with @p symbol concatenated with @p suffix, and possibly an
+    /// underscore and number, if the symbol is already taken.
+    Symbol UniqueSymbolWithSuffix(Symbol symbol, const std::string& suffix) {
+        auto str = ctx.src->Symbols().NameFor(symbol) + suffix;
+        return unique_symbols.GetOrCreate(str, [&] { return b.Symbols().New(str); });
+    }
+
+    /// @returns true if the function @p fn has at least one pointer parameter.
+    static bool HasPointerParameter(const sem::Function* fn) {
+        for (auto* param : fn->Parameters()) {
+            if (param->Type()->Is<sem::Pointer>()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /// @returns true if the function @p fn has at least one pointer parameter in an address space
+    /// that must be replaced. If this function is not called, then the function cannot be sensibly
+    /// generated, and must be stripped.
+    static bool MustBeCalled(const sem::Function* fn) {
+        for (auto* param : fn->Parameters()) {
+            if (auto* ptr = param->Type()->As<sem::Pointer>()) {
+                switch (ptr->AddressSpace()) {
+                    case ast::AddressSpace::kUniform:
+                    case ast::AddressSpace::kStorage:
+                    case ast::AddressSpace::kWorkgroup:
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        }
+        return false;
+    }
+
+    /// @returns true if the given address space is 'private' or 'function'.
+    static bool IsPrivateOrFunction(const ast::AddressSpace sc) {
+        return sc == ast::AddressSpace::kPrivate || sc == ast::AddressSpace::kFunction;
+    }
+};
+
+DirectVariableAccess::Config::Config(const Options& opt) : options(opt) {}
+
+DirectVariableAccess::Config::~Config() = default;
+
+DirectVariableAccess::DirectVariableAccess() = default;
+
+DirectVariableAccess::~DirectVariableAccess() = default;
+
+Transform::ApplyResult DirectVariableAccess::Apply(const Program* program,
+                                                   const DataMap& inputs,
+                                                   DataMap&) const {
+    Options options;
+    if (auto* cfg = inputs.Get<Config>()) {
+        options = cfg->options;
+    }
+    return State(program, options).Run();
+}
+
+}  // namespace tint::transform
diff --git a/src/tint/transform/direct_variable_access.h b/src/tint/transform/direct_variable_access.h
new file mode 100644
index 0000000..34b6e91
--- /dev/null
+++ b/src/tint/transform/direct_variable_access.h
@@ -0,0 +1,74 @@
+// 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_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
+#define SRC_TINT_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint::transform {
+
+/// DirectVariableAccess is a transform that allows usage of pointer parameters in the 'storage',
+/// 'uniform' and 'workgroup' address space, and passing of pointers to sub-objects. These pointers
+/// are only allowed by the resolver when the `chromium_experimental_full_ptr_parameters` extension
+/// is enabled.
+///
+/// DirectVariableAccess works by creating specializations of functions that have pointer
+/// parameters, one specialization for each pointer argument's unique access chain 'shape' from a
+/// unique variable. Calls to specialized functions are transformed so that the pointer arguments
+/// are replaced with an array of access-chain indicies, and if the pointer is in the 'function' or
+/// 'private' address space, also with a pointer to the root object. For more information, see the
+/// comments in src/tint/transform/direct_variable_access.cc.
+///
+/// @note DirectVariableAccess requires the transform::Unshadow transform to have been run first.
+class DirectVariableAccess final : public Castable<DirectVariableAccess, Transform> {
+  public:
+    /// Constructor
+    DirectVariableAccess();
+    /// Destructor
+    ~DirectVariableAccess() override;
+
+    /// Options adjusts the behaviour of the transform.
+    struct Options {
+        /// If true, then 'private' sub-object pointer arguments will be transformed.
+        bool transform_private = false;
+        /// If true, then 'function' sub-object pointer arguments will be transformed.
+        bool transform_function = false;
+    };
+
+    /// Config is consumed by the DirectVariableAccess transform.
+    /// Config specifies the behavior of the transform.
+    struct Config final : public Castable<Data, transform::Data> {
+        /// Constructor
+        /// @param options behavior of the transform
+        explicit Config(const Options& options);
+        /// Destructor
+        ~Config() override;
+
+        /// The transform behavior options
+        const Options options;
+    };
+
+    /// @copydoc Transform::Apply
+    ApplyResult Apply(const Program* program,
+                      const DataMap& inputs,
+                      DataMap& outputs) const override;
+
+  private:
+    struct State;
+};
+
+}  // namespace tint::transform
+
+#endif  // SRC_TINT_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
diff --git a/src/tint/transform/direct_variable_access_test.cc b/src/tint/transform/direct_variable_access_test.cc
new file mode 100644
index 0000000..4c112d4
--- /dev/null
+++ b/src/tint/transform/direct_variable_access_test.cc
@@ -0,0 +1,2697 @@
+// 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.
+
+#include "src/tint/transform/direct_variable_access.h"
+
+#include <memory>
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint::transform {
+namespace {
+
+/// @returns a DataMap with DirectVariableAccess::Config::transform_private enabled.
+static DataMap EnablePrivate() {
+    DirectVariableAccess::Options opts;
+    opts.transform_private = true;
+
+    DataMap inputs;
+    inputs.Add<DirectVariableAccess::Config>(opts);
+    return inputs;
+}
+
+/// @returns a DataMap with DirectVariableAccess::Config::transform_function enabled.
+static DataMap EnableFunction() {
+    DirectVariableAccess::Options opts;
+    opts.transform_function = true;
+
+    DataMap inputs;
+    inputs.Add<DirectVariableAccess::Config>(opts);
+    return inputs;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ShouldRun tests
+////////////////////////////////////////////////////////////////////////////////
+namespace should_run {
+
+using DirectVariableAccessShouldRunTest = TransformTest;
+
+TEST_F(DirectVariableAccessShouldRunTest, EmptyModule) {
+    auto* src = R"()";
+
+    EXPECT_FALSE(ShouldRun<DirectVariableAccess>(src));
+}
+
+}  // namespace should_run
+
+////////////////////////////////////////////////////////////////////////////////
+// remove uncalled
+////////////////////////////////////////////////////////////////////////////////
+namespace remove_uncalled {
+
+using DirectVariableAccessRemoveUncalledTest = TransformTest;
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrUniform) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+
+fn u(pre : i32, p : ptr<uniform, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrStorage) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+
+fn s(pre : i32, p : ptr<storage, i32>, post : i32) -> i32 {
+  return *(p);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrWorkgroup) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+
+fn w(pre : i32, p : ptr<workgroup, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrPrivate_Disabled) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+
+fn f(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrPrivate_Enabled) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrFunction_Disabled) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+
+fn f(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *(p);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrFunction_Enabled) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace remove_uncalled
+
+////////////////////////////////////////////////////////////////////////////////
+// pointer chains
+////////////////////////////////////////////////////////////////////////////////
+namespace pointer_chains_tests {
+
+using DirectVariableAccessPtrChainsTest = TransformTest;
+
+TEST_F(DirectVariableAccessPtrChainsTest, ConstantIndices) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let p0 = &U;
+  let p1 = &(*p0)[1];
+  let p2 = &(*p1)[1+1];
+  let p3 = &(*p2)[2*2 - 1];
+  a(10, p3, 20);
+}
+
+fn c(p : ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>) {
+  let p0 = p;
+  let p1 = &(*p0)[1];
+  let p2 = &(*p1)[1+1];
+  let p3 = &(*p2)[2*2 - 1];
+  a(10, p3, 20);
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+type U_X_X_X = array<u32, 3u>;
+
+fn a_U_X_X_X(pre : i32, p : U_X_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]][p[2]];
+}
+
+fn b() {
+  let p0 = &(U);
+  let p1 = &((*(p0))[1]);
+  let p2 = &((*(p1))[(1 + 1)]);
+  let p3 = &((*(p2))[((2 * 2) - 1)]);
+  a_U_X_X_X(10, U_X_X_X(1, 2, 3), 20);
+}
+
+fn c_U() {
+  let p0 = &(U);
+  let p1 = &(U[1]);
+  let p2 = &(U[1][2]);
+  let p3 = &(U[1][2][3]);
+  a_U_X_X_X(10, U_X_X_X(1, 2, 3), 20);
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPtrChainsTest, HoistIndices) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+  i++;
+  return i;
+}
+fn second() -> i32 {
+  i++;
+  return i;
+}
+fn third() -> i32 {
+  i++;
+  return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let p0 = &U;
+  let p1 = &(*p0)[first()];
+  let p2 = &(*p1)[second()][third()];
+  a(10, p2, 20);
+}
+
+fn c(p : ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>) {
+  let p0 = &U;
+  let p1 = &(*p0)[first()];
+  let p2 = &(*p1)[second()][third()];
+  a(10, p2, 20);
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+  i++;
+  return i;
+}
+
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+fn third() -> i32 {
+  i++;
+  return i;
+}
+
+type U_X_X_X = array<u32, 3u>;
+
+fn a_U_X_X_X(pre : i32, p : U_X_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]][p[2]];
+}
+
+fn b() {
+  let p0 = &(U);
+  let ptr_index_save = first();
+  let p1 = &((*(p0))[ptr_index_save]);
+  let ptr_index_save_1 = second();
+  let ptr_index_save_2 = third();
+  let p2 = &((*(p1))[ptr_index_save_1][ptr_index_save_2]);
+  a_U_X_X_X(10, U_X_X_X(u32(ptr_index_save), u32(ptr_index_save_1), u32(ptr_index_save_2)), 20);
+}
+
+fn c_U() {
+  let p0 = &(U);
+  let ptr_index_save_3 = first();
+  let p1 = &((*(p0))[ptr_index_save_3]);
+  let ptr_index_save_4 = second();
+  let ptr_index_save_5 = third();
+  let p2 = &((*(p1))[ptr_index_save_4][ptr_index_save_5]);
+  a_U_X_X_X(10, U_X_X_X(u32(ptr_index_save_3), u32(ptr_index_save_4), u32(ptr_index_save_5)), 20);
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPtrChainsTest, HoistInForLoopInit) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+  i++;
+  return i;
+}
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  for (let p1 = &U[first()]; true; ) {
+    a(10, &(*p1)[second()], 20);
+  }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+  for (let p1 = &(*p)[first()]; true; ) {
+    a(10, &(*p1)[second()], 20);
+  }
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+  i++;
+  return i;
+}
+
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+type U_X_X = array<u32, 2u>;
+
+fn a_U_X_X(pre : i32, p : U_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]];
+}
+
+fn b() {
+  let ptr_index_save = first();
+  for(let p1 = &(U[ptr_index_save]); true; ) {
+    a_U_X_X(10, U_X_X(u32(ptr_index_save), u32(second())), 20);
+  }
+}
+
+fn c_U() {
+  let ptr_index_save_1 = first();
+  for(let p1 = &(U[ptr_index_save_1]); true; ) {
+    a_U_X_X(10, U_X_X(u32(ptr_index_save_1), u32(second())), 20);
+  }
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPtrChainsTest, HoistInForLoopCond) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+  i++;
+  return i;
+}
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let p = &U[first()][second()];
+  for (; a(10, p, 20).x < 4; ) {
+    let body = 1;
+  }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+  let p2 = &(*p)[first()][second()];
+  for (; a(10, p2, 20).x < 4; ) {
+    let body = 1;
+  }
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+  i++;
+  return i;
+}
+
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+type U_X_X = array<u32, 2u>;
+
+fn a_U_X_X(pre : i32, p : U_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]];
+}
+
+fn b() {
+  let ptr_index_save = first();
+  let ptr_index_save_1 = second();
+  let p = &(U[ptr_index_save][ptr_index_save_1]);
+  for(; (a_U_X_X(10, U_X_X(u32(ptr_index_save), u32(ptr_index_save_1)), 20).x < 4); ) {
+    let body = 1;
+  }
+}
+
+fn c_U() {
+  let ptr_index_save_2 = first();
+  let ptr_index_save_3 = second();
+  let p2 = &(U[ptr_index_save_2][ptr_index_save_3]);
+  for(; (a_U_X_X(10, U_X_X(u32(ptr_index_save_2), u32(ptr_index_save_3)), 20).x < 4); ) {
+    let body = 1;
+  }
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPtrChainsTest, HoistInForLoopCont) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+  i++;
+  return i;
+}
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let p = &U[first()][second()];
+  for (var i = 0; i < 3; a(10, p, 20)) {
+    i++;
+  }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+  let p2 = &(*p)[first()][second()];
+  for (var i = 0; i < 3; a(10, p2, 20)) {
+    i++;
+  }
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+  i++;
+  return i;
+}
+
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+type U_X_X = array<u32, 2u>;
+
+fn a_U_X_X(pre : i32, p : U_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]];
+}
+
+fn b() {
+  let ptr_index_save = first();
+  let ptr_index_save_1 = second();
+  let p = &(U[ptr_index_save][ptr_index_save_1]);
+  for(var i = 0; (i < 3); a_U_X_X(10, U_X_X(u32(ptr_index_save), u32(ptr_index_save_1)), 20)) {
+    i++;
+  }
+}
+
+fn c_U() {
+  let ptr_index_save_2 = first();
+  let ptr_index_save_3 = second();
+  let p2 = &(U[ptr_index_save_2][ptr_index_save_3]);
+  for(var i = 0; (i < 3); a_U_X_X(10, U_X_X(u32(ptr_index_save_2), u32(ptr_index_save_3)), 20)) {
+    i++;
+  }
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPtrChainsTest, HoistInWhileCond) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+  i++;
+  return i;
+}
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let p = &U[first()][second()];
+  while (a(10, p, 20).x < 4) {
+    let body = 1;
+  }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+  let p2 = &(*p)[first()][second()];
+  while (a(10, p2, 20).x < 4) {
+    let body = 1;
+  }
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+  i++;
+  return i;
+}
+
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+type U_X_X = array<u32, 2u>;
+
+fn a_U_X_X(pre : i32, p : U_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]];
+}
+
+fn b() {
+  let ptr_index_save = first();
+  let ptr_index_save_1 = second();
+  let p = &(U[ptr_index_save][ptr_index_save_1]);
+  while((a_U_X_X(10, U_X_X(u32(ptr_index_save), u32(ptr_index_save_1)), 20).x < 4)) {
+    let body = 1;
+  }
+}
+
+fn c_U() {
+  let ptr_index_save_2 = first();
+  let ptr_index_save_3 = second();
+  let p2 = &(U[ptr_index_save_2][ptr_index_save_3]);
+  while((a_U_X_X(10, U_X_X(u32(ptr_index_save_2), u32(ptr_index_save_3)), 20).x < 4)) {
+    let body = 1;
+  }
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace pointer_chains_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'uniform' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace uniform_as_tests {
+
+using DirectVariableAccessUniformASTest = TransformTest;
+
+TEST_F(DirectVariableAccessUniformASTest, Param_ptr_i32_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : i32;
+
+fn a(pre : i32, p : ptr<uniform, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+fn b() {
+  a(10, &U, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : i32;
+
+fn a_U(pre : i32, post : i32) -> i32 {
+  return U;
+}
+
+fn b() {
+  a_U(10, 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessUniformASTest, Param_ptr_vec4i32_Via_array_DynamicRead) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let I = 3;
+  a(10, &U[I], 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<vec4<i32>, 8>;
+
+type U_X = array<u32, 1u>;
+
+fn a_U_X(pre : i32, p : U_X, post : i32) -> vec4<i32> {
+  return U[p[0]];
+}
+
+fn b() {
+  let I = 3;
+  a_U_X(10, U_X(u32(I)), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessUniformASTest, CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+};
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+};
+
+@group(0) @binding(0) var<uniform> U : Outer;
+
+fn f0(p : ptr<uniform, vec4<f32>>) -> f32 {
+  return (*p).x;
+}
+
+fn f1(p : ptr<uniform, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    // call f0() with inline usage of p
+    res += f0(&(*p)[1]);
+  }
+  {
+    // call f0() with pointer-let usage of p
+    let p_vec = &(*p)[1];
+    res += f0(p_vec);
+  }
+  {
+    // call f0() with inline usage of U
+    res += f0(&U.arr[2].mat[1]);
+  }
+  {
+    // call f0() with pointer-let usage of U
+    let p_vec = &U.arr[2].mat[1];
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<uniform, Inner>) -> f32 {
+  let p_mat = &(*p).mat;
+  return f1(p_mat);
+}
+
+fn f3(p0 : ptr<uniform, InnerArr>, p1 : ptr<uniform, mat3x4<f32>>) -> f32 {
+  let p0_inner = &(*p0)[3];
+  return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<uniform, Outer>) -> f32 {
+  return f3(&(*p).arr, &U.mat);
+}
+
+fn b() {
+  f4(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+@group(0) @binding(0) var<uniform> U : Outer;
+
+type U_mat_X = array<u32, 1u>;
+
+fn f0_U_mat_X(p : U_mat_X) -> f32 {
+  return U.mat[p[0]].x;
+}
+
+type U_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_U_arr_X_mat_X(p : U_arr_X_mat_X) -> f32 {
+  return U.arr[p[0]].mat[p[0]].x;
+}
+
+type U_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_U_arr_X_mat_X_1(p : U_arr_X_mat_X_1) -> f32 {
+  return U.arr[p[0]].mat[p[1]].x;
+}
+
+fn f1_U_mat() -> f32 {
+  var res : f32;
+  {
+    res += f0_U_mat_X(U_mat_X(1));
+  }
+  {
+    let p_vec = &(U.mat[1]);
+    res += f0_U_mat_X(U_mat_X(1));
+  }
+  {
+    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(U.arr[2].mat[1]);
+    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type U_arr_X_mat = array<u32, 1u>;
+
+fn f1_U_arr_X_mat(p : U_arr_X_mat) -> f32 {
+  var res : f32;
+  {
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    let p_vec = &(U.arr[p[0]].mat[1]);
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(U.arr[2].mat[1]);
+    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type U_arr_X = array<u32, 1u>;
+
+fn f2_U_arr_X(p : U_arr_X) -> f32 {
+  let p_mat = &(U.arr[p[0]].mat);
+  return f1_U_arr_X_mat(U_arr_X_mat(p[0u]));
+}
+
+fn f3_U_arr_U_mat() -> f32 {
+  let p0_inner = &(U.arr[3]);
+  return (f2_U_arr_X(U_arr_X(3)) + f1_U_mat());
+}
+
+fn f4_U() -> f32 {
+  return f3_U_arr_U_mat();
+}
+
+fn b() {
+  f4_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace uniform_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'storage' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace storage_as_tests {
+
+using DirectVariableAccessStorageASTest = TransformTest;
+
+TEST_F(DirectVariableAccessStorageASTest, Param_ptr_i32_Via_struct_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn a(pre : i32, p : ptr<storage, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+fn b() {
+  a(10, &S.i, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn a_S_i(pre : i32, post : i32) -> i32 {
+  return S.i;
+}
+
+fn b() {
+  a_S_i(10, 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessStorageASTest, Param_ptr_arr_i32_Via_struct_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn a(pre : i32, p : ptr<storage, array<i32, 4>, read_write>, post : i32) {
+  *p = array<i32, 4>();
+}
+
+fn b() {
+  a(10, &S.arr, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn a_S_arr(pre : i32, post : i32) {
+  S.arr = array<i32, 4>();
+}
+
+fn b() {
+  a_S_arr(10, 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessStorageASTest, Param_ptr_vec4i32_Via_array_DynamicWrite) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<storage, vec4<i32>, read_write>, post : i32) {
+  *p = vec4<i32>();
+}
+
+fn b() {
+  let I = 3;
+  a(10, &S[I], 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : array<vec4<i32>, 8>;
+
+type S_X = array<u32, 1u>;
+
+fn a_S_X(pre : i32, p : S_X, post : i32) {
+  S[p[0]] = vec4<i32>();
+}
+
+fn b() {
+  let I = 3;
+  a_S_X(10, S_X(u32(I)), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessStorageASTest, CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+};
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+};
+
+@group(0) @binding(0) var<storage> S : Outer;
+
+fn f0(p : ptr<storage, vec4<f32>>) -> f32 {
+  return (*p).x;
+}
+
+fn f1(p : ptr<storage, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    // call f0() with inline usage of p
+    res += f0(&(*p)[1]);
+  }
+  {
+    // call f0() with pointer-let usage of p
+    let p_vec = &(*p)[1];
+    res += f0(p_vec);
+  }
+  {
+    // call f0() with inline usage of S
+    res += f0(&S.arr[2].mat[1]);
+  }
+  {
+    // call f0() with pointer-let usage of S
+    let p_vec = &S.arr[2].mat[1];
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<storage, Inner>) -> f32 {
+  let p_mat = &(*p).mat;
+  return f1(p_mat);
+}
+
+fn f3(p0 : ptr<storage, InnerArr>, p1 : ptr<storage, mat3x4<f32>>) -> f32 {
+  let p0_inner = &(*p0)[3];
+  return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<storage, Outer>) -> f32 {
+  return f3(&(*p).arr, &S.mat);
+}
+
+fn b() {
+  f4(&S);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+@group(0) @binding(0) var<storage> S : Outer;
+
+type S_mat_X = array<u32, 1u>;
+
+fn f0_S_mat_X(p : S_mat_X) -> f32 {
+  return S.mat[p[0]].x;
+}
+
+type S_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_S_arr_X_mat_X(p : S_arr_X_mat_X) -> f32 {
+  return S.arr[p[0]].mat[p[0]].x;
+}
+
+type S_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_S_arr_X_mat_X_1(p : S_arr_X_mat_X_1) -> f32 {
+  return S.arr[p[0]].mat[p[1]].x;
+}
+
+fn f1_S_mat() -> f32 {
+  var res : f32;
+  {
+    res += f0_S_mat_X(S_mat_X(1));
+  }
+  {
+    let p_vec = &(S.mat[1]);
+    res += f0_S_mat_X(S_mat_X(1));
+  }
+  {
+    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(S.arr[2].mat[1]);
+    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type S_arr_X_mat = array<u32, 1u>;
+
+fn f1_S_arr_X_mat(p : S_arr_X_mat) -> f32 {
+  var res : f32;
+  {
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    let p_vec = &(S.arr[p[0]].mat[1]);
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(S.arr[2].mat[1]);
+    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type S_arr_X = array<u32, 1u>;
+
+fn f2_S_arr_X(p : S_arr_X) -> f32 {
+  let p_mat = &(S.arr[p[0]].mat);
+  return f1_S_arr_X_mat(S_arr_X_mat(p[0u]));
+}
+
+fn f3_S_arr_S_mat() -> f32 {
+  let p0_inner = &(S.arr[3]);
+  return (f2_S_arr_X(S_arr_X(3)) + f1_S_mat());
+}
+
+fn f4_S() -> f32 {
+  return f3_S_arr_S_mat();
+}
+
+fn b() {
+  f4_S();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace storage_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'workgroup' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace workgroup_as_tests {
+
+using DirectVariableAccessWorkgroupASTest = TransformTest;
+
+TEST_F(DirectVariableAccessWorkgroupASTest, Param_ptr_vec4i32_Via_array_StaticRead) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<workgroup, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  a(10, &W[3], 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : array<vec4<i32>, 8>;
+
+type W_X = array<u32, 1u>;
+
+fn a_W_X(pre : i32, p : W_X, post : i32) -> vec4<i32> {
+  return W[p[0]];
+}
+
+fn b() {
+  a_W_X(10, W_X(3), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessWorkgroupASTest, Param_ptr_vec4i32_Via_array_StaticWrite) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<workgroup, vec4<i32>>, post : i32) {
+  *p = vec4<i32>();
+}
+
+fn b() {
+  a(10, &W[3], 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : array<vec4<i32>, 8>;
+
+type W_X = array<u32, 1u>;
+
+fn a_W_X(pre : i32, p : W_X, post : i32) {
+  W[p[0]] = vec4<i32>();
+}
+
+fn b() {
+  a_W_X(10, W_X(3), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessWorkgroupASTest, CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+};
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+};
+
+var<workgroup> W : Outer;
+
+fn f0(p : ptr<workgroup, vec4<f32>>) -> f32 {
+  return (*p).x;
+}
+
+fn f1(p : ptr<workgroup, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    // call f0() with inline usage of p
+    res += f0(&(*p)[1]);
+  }
+  {
+    // call f0() with pointer-let usage of p
+    let p_vec = &(*p)[1];
+    res += f0(p_vec);
+  }
+  {
+    // call f0() with inline usage of W
+    res += f0(&W.arr[2].mat[1]);
+  }
+  {
+    // call f0() with pointer-let usage of W
+    let p_vec = &W.arr[2].mat[1];
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<workgroup, Inner>) -> f32 {
+  let p_mat = &(*p).mat;
+  return f1(p_mat);
+}
+
+fn f3(p0 : ptr<workgroup, InnerArr>, p1 : ptr<workgroup, mat3x4<f32>>) -> f32 {
+  let p0_inner = &(*p0)[3];
+  return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<workgroup, Outer>) -> f32 {
+  return f3(&(*p).arr, &W.mat);
+}
+
+fn b() {
+  f4(&W);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+var<workgroup> W : Outer;
+
+type W_mat_X = array<u32, 1u>;
+
+fn f0_W_mat_X(p : W_mat_X) -> f32 {
+  return W.mat[p[0]].x;
+}
+
+type W_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_W_arr_X_mat_X(p : W_arr_X_mat_X) -> f32 {
+  return W.arr[p[0]].mat[p[0]].x;
+}
+
+type W_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_W_arr_X_mat_X_1(p : W_arr_X_mat_X_1) -> f32 {
+  return W.arr[p[0]].mat[p[1]].x;
+}
+
+fn f1_W_mat() -> f32 {
+  var res : f32;
+  {
+    res += f0_W_mat_X(W_mat_X(1));
+  }
+  {
+    let p_vec = &(W.mat[1]);
+    res += f0_W_mat_X(W_mat_X(1));
+  }
+  {
+    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(W.arr[2].mat[1]);
+    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type W_arr_X_mat = array<u32, 1u>;
+
+fn f1_W_arr_X_mat(p : W_arr_X_mat) -> f32 {
+  var res : f32;
+  {
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    let p_vec = &(W.arr[p[0]].mat[1]);
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(W.arr[2].mat[1]);
+    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type W_arr_X = array<u32, 1u>;
+
+fn f2_W_arr_X(p : W_arr_X) -> f32 {
+  let p_mat = &(W.arr[p[0]].mat);
+  return f1_W_arr_X_mat(W_arr_X_mat(p[0u]));
+}
+
+fn f3_W_arr_W_mat() -> f32 {
+  let p0_inner = &(W.arr[3]);
+  return (f2_W_arr_X(W_arr_X(3)) + f1_W_mat());
+}
+
+fn f4_W() -> f32 {
+  return f3_W_arr_W_mat();
+}
+
+fn b() {
+  f4_W();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace workgroup_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'private' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace private_as_tests {
+
+using DirectVariableAccessPrivateASTest = TransformTest;
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_Param_ptr_i32_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+var<private> P : i32;
+
+fn b() {
+  a(10, &(P), 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a_F(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+var<private> P : i32;
+
+fn b() {
+  a_F(10, &(P), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_Param_ptr_i32_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) {
+  *(p) = 42;
+}
+
+var<private> P : i32;
+
+fn b() {
+  a(10, &(P), 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a_F(pre : i32, p : ptr<private, i32>, post : i32) {
+  *(p) = 42;
+}
+
+var<private> P : i32;
+
+fn b() {
+  a_F(10, &(P), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_Param_ptr_i32_Via_struct_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+var<private> P : str;
+
+fn b() {
+  a(10, &P.i, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a_F_i(pre : i32, p : ptr<private, str>, post : i32) -> i32 {
+  return (*(p)).i;
+}
+
+var<private> P : str;
+
+fn b() {
+  a_F_i(10, &(P), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Disabled_Param_ptr_i32_Via_struct_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+var<private> P : str;
+
+fn b() {
+  a(10, &(P.i), 20);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_Param_ptr_arr_i32_Via_struct_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+fn a(pre : i32, p : ptr<private, array<i32, 4>>, post : i32) {
+  *p = array<i32, 4>();
+}
+
+var<private> P : str;
+
+fn b() {
+  a(10, &P.arr, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn a_F_arr(pre : i32, p : ptr<private, str>, post : i32) {
+  (*(p)).arr = array<i32, 4>();
+}
+
+var<private> P : str;
+
+fn b() {
+  a_F_arr(10, &(P), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Disabled_Param_ptr_arr_i32_Via_struct_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn a(pre : i32, p : ptr<private, array<i32, 4>>, post : i32) {
+  *(p) = array<i32, 4>();
+}
+
+var<private> P : str;
+
+fn b() {
+  a(10, &(P.arr), 20);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_Param_ptr_i32_mixed) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+var<private> Pi : i32;
+var<private> Ps : str;
+var<private> Pa : array<i32, 4>;
+
+fn b() {
+  a(10, &Pi, 20);
+  a(30, &Ps.i, 40);
+  a(50, &Pa[2], 60);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a_F(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+fn a_F_i(pre : i32, p : ptr<private, str>, post : i32) -> i32 {
+  return (*(p)).i;
+}
+
+type F_X = array<u32, 1u>;
+
+fn a_F_X(pre : i32, p_base : ptr<private, array<i32, 4u>>, p_indices : F_X, post : i32) -> i32 {
+  return (*(p_base))[p_indices[0]];
+}
+
+var<private> Pi : i32;
+
+var<private> Ps : str;
+
+var<private> Pa : array<i32, 4>;
+
+type F_X_1 = array<u32, 1u>;
+
+fn b() {
+  a_F(10, &(Pi), 20);
+  a_F_i(30, &(Ps), 40);
+  a_F_X(50, &(Pa), F_X_1(2), 60);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Disabled_Param_ptr_i32_mixed) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+var<private> Pi : i32;
+
+var<private> Ps : str;
+
+var<private> Pa : array<i32, 4>;
+
+fn b() {
+  a(10, &(Pi), 20);
+  a(10, &(Ps.i), 20);
+  a(10, &(Pa[2]), 20);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+};
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+};
+
+var<private> P : Outer;
+
+fn f0(p : ptr<private, vec4<f32>>) -> f32 {
+  return (*p).x;
+}
+
+fn f1(p : ptr<private, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    // call f0() with inline usage of p
+    res += f0(&(*p)[1]);
+  }
+  {
+    // call f0() with pointer-let usage of p
+    let p_vec = &(*p)[1];
+    res += f0(p_vec);
+  }
+  {
+    // call f0() with inline usage of P
+    res += f0(&P.arr[2].mat[1]);
+  }
+  {
+    // call f0() with pointer-let usage of P
+    let p_vec = &P.arr[2].mat[1];
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<private, Inner>) -> f32 {
+  let p_mat = &(*p).mat;
+  return f1(p_mat);
+}
+
+fn f3(p0 : ptr<private, InnerArr>, p1 : ptr<private, mat3x4<f32>>) -> f32 {
+  let p0_inner = &(*p0)[3];
+  return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<private, Outer>) -> f32 {
+  return f3(&(*p).arr, &P.mat);
+}
+
+fn b() {
+  f4(&P);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+var<private> P : Outer;
+
+type F_mat_X = array<u32, 1u>;
+
+fn f0_F_mat_X(p_base : ptr<private, Outer>, p_indices : F_mat_X) -> f32 {
+  return (*(p_base)).mat[p_indices[0]].x;
+}
+
+type F_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_F_arr_X_mat_X(p_base : ptr<private, Outer>, p_indices : F_arr_X_mat_X) -> f32 {
+  return (*(p_base)).arr[p_indices[0]].mat[p_indices[0]].x;
+}
+
+type F_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_F_arr_X_mat_X_1(p_base : ptr<private, Outer>, p_indices : F_arr_X_mat_X_1) -> f32 {
+  return (*(p_base)).arr[p_indices[0]].mat[p_indices[1]].x;
+}
+
+type F_mat_X_1 = array<u32, 1u>;
+
+type F_arr_X_mat_X_2 = array<u32, 2u>;
+
+fn f1_F_mat(p : ptr<private, Outer>) -> f32 {
+  var res : f32;
+  {
+    res += f0_F_mat_X(p, F_mat_X_1(1));
+  }
+  {
+    let p_vec = &((*(p)).mat[1]);
+    res += f0_F_mat_X(p, F_mat_X_1(1));
+  }
+  {
+    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+  }
+  {
+    let p_vec = &(P.arr[2].mat[1]);
+    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+  }
+  return res;
+}
+
+type F_arr_X_mat = array<u32, 1u>;
+
+type F_arr_X_mat_X_3 = array<u32, 2u>;
+
+fn f1_F_arr_X_mat(p_base : ptr<private, Outer>, p_indices : F_arr_X_mat) -> f32 {
+  var res : f32;
+  {
+    res += f0_F_arr_X_mat_X(p_base, F_arr_X_mat_X_3(p_indices[0u], 1));
+  }
+  {
+    let p_vec = &((*(p_base)).arr[p_indices[0]].mat[1]);
+    res += f0_F_arr_X_mat_X(p_base, F_arr_X_mat_X_3(p_indices[0u], 1));
+  }
+  {
+    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+  }
+  {
+    let p_vec = &(P.arr[2].mat[1]);
+    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+  }
+  return res;
+}
+
+type F_arr_X = array<u32, 1u>;
+
+type F_arr_X_mat_1 = array<u32, 1u>;
+
+fn f2_F_arr_X(p_base : ptr<private, Outer>, p_indices : F_arr_X) -> f32 {
+  let p_mat = &((*(p_base)).arr[p_indices[0]].mat);
+  return f1_F_arr_X_mat(p_base, F_arr_X_mat_1(p_indices[0u]));
+}
+
+type F_arr_X_1 = array<u32, 1u>;
+
+fn f3_F_arr_F_mat(p0 : ptr<private, Outer>, p1 : ptr<private, Outer>) -> f32 {
+  let p0_inner = &((*(p0)).arr[3]);
+  return (f2_F_arr_X(p0, F_arr_X_1(3)) + f1_F_mat(p1));
+}
+
+fn f4_F(p : ptr<private, Outer>) -> f32 {
+  return f3_F_arr_F_mat(p, &(P));
+}
+
+fn b() {
+  f4_F(&(P));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Disabled_CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+var<private> P : Outer;
+
+fn f0(p : ptr<private, vec4<f32>>) -> f32 {
+  return (*(p)).x;
+}
+
+fn f1(p : ptr<private, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    res += f0(&((*(p))[1]));
+  }
+  {
+    let p_vec = &((*(p))[1]);
+    res += f0(p_vec);
+  }
+  {
+    res += f0(&(P.arr[2].mat[1]));
+  }
+  {
+    let p_vec = &(P.arr[2].mat[1]);
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<private, Inner>) -> f32 {
+  let p_mat = &((*(p)).mat);
+  return f1(p_mat);
+}
+
+fn f3(p0 : ptr<private, InnerArr>, p1 : ptr<private, mat3x4<f32>>) -> f32 {
+  let p0_inner = &((*(p0))[3]);
+  return (f2(p0_inner) + f1(p1));
+}
+
+fn f4(p : ptr<private, Outer>) -> f32 {
+  return f3(&((*(p)).arr), &(P.mat));
+}
+
+fn b() {
+  f4(&(P));
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace private_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'function' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace function_as_tests {
+
+using DirectVariableAccessFunctionASTest = TransformTest;
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_LocalPtr) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn f() {
+  var v : i32;
+  let p : ptr<function, i32> = &(v);
+  var x : i32 = *(p);
+}
+)";
+
+    auto* expect = src;  // Nothing changes
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_Param_ptr_i32_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+fn b() {
+  var F : i32;
+  a(10, &(F), 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a_F(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+fn b() {
+  var F : i32;
+  a_F(10, &(F), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_Param_ptr_i32_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) {
+  *(p) = 42;
+}
+
+fn b() {
+  var F : i32;
+  a(10, &(F), 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a_F(pre : i32, p : ptr<function, i32>, post : i32) {
+  *(p) = 42;
+}
+
+fn b() {
+  var F : i32;
+  a_F(10, &(F), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_Param_ptr_i32_Via_struct_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+fn b() {
+  var F : str;
+  a(10, &F.i, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a_F_i(pre : i32, p : ptr<function, str>, post : i32) -> i32 {
+  return (*(p)).i;
+}
+
+fn b() {
+  var F : str;
+  a_F_i(10, &(F), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_Param_ptr_arr_i32_Via_struct_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+fn a(pre : i32, p : ptr<function, array<i32, 4>>, post : i32) {
+  *p = array<i32, 4>();
+}
+
+fn b() {
+  var F : str;
+  a(10, &F.arr, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn a_F_arr(pre : i32, p : ptr<function, str>, post : i32) {
+  (*(p)).arr = array<i32, 4>();
+}
+
+fn b() {
+  var F : str;
+  a_F_arr(10, &(F), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_Param_ptr_i32_mixed) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+fn b() {
+  var Fi : i32;
+  var Fs : str;
+  var Fa : array<i32, 4>;
+
+  a(10, &Fi, 20);
+  a(30, &Fs.i, 40);
+  a(50, &Fa[2], 60);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a_F(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+fn a_F_i(pre : i32, p : ptr<function, str>, post : i32) -> i32 {
+  return (*(p)).i;
+}
+
+type F_X = array<u32, 1u>;
+
+fn a_F_X(pre : i32, p_base : ptr<function, array<i32, 4u>>, p_indices : F_X, post : i32) -> i32 {
+  return (*(p_base))[p_indices[0]];
+}
+
+type F_X_1 = array<u32, 1u>;
+
+fn b() {
+  var Fi : i32;
+  var Fs : str;
+  var Fa : array<i32, 4>;
+  a_F(10, &(Fi), 20);
+  a_F_i(30, &(Fs), 40);
+  a_F_X(50, &(Fa), F_X_1(2), 60);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Disabled_Param_ptr_i32_Via_struct_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+fn b() {
+  var F : str;
+  a(10, &(F.i), 20);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Disabled_Param_ptr_arr_i32_Via_struct_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn a(pre : i32, p : ptr<function, array<i32, 4>>, post : i32) {
+  *(p) = array<i32, 4>();
+}
+
+fn b() {
+  var F : str;
+  a(10, &(F.arr), 20);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace function_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// complex tests
+////////////////////////////////////////////////////////////////////////////////
+namespace complex_tests {
+
+using DirectVariableAccessComplexTest = TransformTest;
+
+TEST_F(DirectVariableAccessComplexTest, Param_ptr_mixed_vec4i32_ViaMultiple) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<i32>,
+};
+
+@group(0) @binding(0) var<uniform> U     : vec4<i32>;
+@group(0) @binding(1) var<uniform> U_str   : str;
+@group(0) @binding(2) var<uniform> U_arr   : array<vec4<i32>, 8>;
+@group(0) @binding(3) var<uniform> U_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+@group(1) @binding(0) var<storage> S     : vec4<i32>;
+@group(1) @binding(1) var<storage> S_str   : str;
+@group(1) @binding(2) var<storage> S_arr   : array<vec4<i32>, 8>;
+@group(1) @binding(3) var<storage> S_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+          var<workgroup> W     : vec4<i32>;
+          var<workgroup> W_str   : str;
+          var<workgroup> W_arr   : array<vec4<i32>, 8>;
+          var<workgroup> W_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+fn fn_u(p : ptr<uniform, vec4<i32>>) -> vec4<i32> {
+  return *p;
+}
+
+fn fn_s(p : ptr<storage, vec4<i32>>) -> vec4<i32> {
+  return *p;
+}
+
+fn fn_w(p : ptr<workgroup, vec4<i32>>) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let I = 3;
+  let J = 4;
+
+  let u           = fn_u(&U);
+  let u_str       = fn_u(&U_str.i);
+  let u_arr0      = fn_u(&U_arr[0]);
+  let u_arr1      = fn_u(&U_arr[1]);
+  let u_arrI      = fn_u(&U_arr[I]);
+  let u_arr1_arr0 = fn_u(&U_arr_arr[1][0]);
+  let u_arr2_arrI = fn_u(&U_arr_arr[2][I]);
+  let u_arrI_arr2 = fn_u(&U_arr_arr[I][2]);
+  let u_arrI_arrJ = fn_u(&U_arr_arr[I][J]);
+
+  let s           = fn_s(&S);
+  let s_str       = fn_s(&S_str.i);
+  let s_arr0      = fn_s(&S_arr[0]);
+  let s_arr1      = fn_s(&S_arr[1]);
+  let s_arrI      = fn_s(&S_arr[I]);
+  let s_arr1_arr0 = fn_s(&S_arr_arr[1][0]);
+  let s_arr2_arrI = fn_s(&S_arr_arr[2][I]);
+  let s_arrI_arr2 = fn_s(&S_arr_arr[I][2]);
+  let s_arrI_arrJ = fn_s(&S_arr_arr[I][J]);
+
+  let w           = fn_w(&W);
+  let w_str       = fn_w(&W_str.i);
+  let w_arr0      = fn_w(&W_arr[0]);
+  let w_arr1      = fn_w(&W_arr[1]);
+  let w_arrI      = fn_w(&W_arr[I]);
+  let w_arr1_arr0 = fn_w(&W_arr_arr[1][0]);
+  let w_arr2_arrI = fn_w(&W_arr_arr[2][I]);
+  let w_arrI_arr2 = fn_w(&W_arr_arr[I][2]);
+  let w_arrI_arrJ = fn_w(&W_arr_arr[I][J]);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<i32>,
+}
+
+@group(0) @binding(0) var<uniform> U : vec4<i32>;
+
+@group(0) @binding(1) var<uniform> U_str : str;
+
+@group(0) @binding(2) var<uniform> U_arr : array<vec4<i32>, 8>;
+
+@group(0) @binding(3) var<uniform> U_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+@group(1) @binding(0) var<storage> S : vec4<i32>;
+
+@group(1) @binding(1) var<storage> S_str : str;
+
+@group(1) @binding(2) var<storage> S_arr : array<vec4<i32>, 8>;
+
+@group(1) @binding(3) var<storage> S_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+var<workgroup> W : vec4<i32>;
+
+var<workgroup> W_str : str;
+
+var<workgroup> W_arr : array<vec4<i32>, 8>;
+
+var<workgroup> W_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+fn fn_u_U() -> vec4<i32> {
+  return U;
+}
+
+fn fn_u_U_str_i() -> vec4<i32> {
+  return U_str.i;
+}
+
+type U_arr_X = array<u32, 1u>;
+
+fn fn_u_U_arr_X(p : U_arr_X) -> vec4<i32> {
+  return U_arr[p[0]];
+}
+
+type U_arr_arr_X_X = array<u32, 2u>;
+
+fn fn_u_U_arr_arr_X_X(p : U_arr_arr_X_X) -> vec4<i32> {
+  return U_arr_arr[p[0]][p[1]];
+}
+
+fn fn_s_S() -> vec4<i32> {
+  return S;
+}
+
+fn fn_s_S_str_i() -> vec4<i32> {
+  return S_str.i;
+}
+
+type S_arr_X = array<u32, 1u>;
+
+fn fn_s_S_arr_X(p : S_arr_X) -> vec4<i32> {
+  return S_arr[p[0]];
+}
+
+type S_arr_arr_X_X = array<u32, 2u>;
+
+fn fn_s_S_arr_arr_X_X(p : S_arr_arr_X_X) -> vec4<i32> {
+  return S_arr_arr[p[0]][p[1]];
+}
+
+fn fn_w_W() -> vec4<i32> {
+  return W;
+}
+
+fn fn_w_W_str_i() -> vec4<i32> {
+  return W_str.i;
+}
+
+type W_arr_X = array<u32, 1u>;
+
+fn fn_w_W_arr_X(p : W_arr_X) -> vec4<i32> {
+  return W_arr[p[0]];
+}
+
+type W_arr_arr_X_X = array<u32, 2u>;
+
+fn fn_w_W_arr_arr_X_X(p : W_arr_arr_X_X) -> vec4<i32> {
+  return W_arr_arr[p[0]][p[1]];
+}
+
+fn b() {
+  let I = 3;
+  let J = 4;
+  let u = fn_u_U();
+  let u_str = fn_u_U_str_i();
+  let u_arr0 = fn_u_U_arr_X(U_arr_X(0));
+  let u_arr1 = fn_u_U_arr_X(U_arr_X(1));
+  let u_arrI = fn_u_U_arr_X(U_arr_X(u32(I)));
+  let u_arr1_arr0 = fn_u_U_arr_arr_X_X(U_arr_arr_X_X(1, 0));
+  let u_arr2_arrI = fn_u_U_arr_arr_X_X(U_arr_arr_X_X(2, u32(I)));
+  let u_arrI_arr2 = fn_u_U_arr_arr_X_X(U_arr_arr_X_X(u32(I), 2));
+  let u_arrI_arrJ = fn_u_U_arr_arr_X_X(U_arr_arr_X_X(u32(I), u32(J)));
+  let s = fn_s_S();
+  let s_str = fn_s_S_str_i();
+  let s_arr0 = fn_s_S_arr_X(S_arr_X(0));
+  let s_arr1 = fn_s_S_arr_X(S_arr_X(1));
+  let s_arrI = fn_s_S_arr_X(S_arr_X(u32(I)));
+  let s_arr1_arr0 = fn_s_S_arr_arr_X_X(S_arr_arr_X_X(1, 0));
+  let s_arr2_arrI = fn_s_S_arr_arr_X_X(S_arr_arr_X_X(2, u32(I)));
+  let s_arrI_arr2 = fn_s_S_arr_arr_X_X(S_arr_arr_X_X(u32(I), 2));
+  let s_arrI_arrJ = fn_s_S_arr_arr_X_X(S_arr_arr_X_X(u32(I), u32(J)));
+  let w = fn_w_W();
+  let w_str = fn_w_W_str_i();
+  let w_arr0 = fn_w_W_arr_X(W_arr_X(0));
+  let w_arr1 = fn_w_W_arr_X(W_arr_X(1));
+  let w_arrI = fn_w_W_arr_X(W_arr_X(u32(I)));
+  let w_arr1_arr0 = fn_w_W_arr_arr_X_X(W_arr_arr_X_X(1, 0));
+  let w_arr2_arrI = fn_w_W_arr_arr_X_X(W_arr_arr_X_X(2, u32(I)));
+  let w_arrI_arr2 = fn_w_W_arr_arr_X_X(W_arr_arr_X_X(u32(I), 2));
+  let w_arrI_arrJ = fn_w_W_arr_arr_X_X(W_arr_arr_X_X(u32(I), u32(J)));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessComplexTest, Indexing) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<array<i32, 9>, 9>, 9>, 50>;
+
+fn a(i : i32) -> i32 { return i; }
+
+fn b(p : ptr<storage, array<array<array<i32, 9>, 9>, 9>>) -> i32 {
+  return (*p) [ a( (*p)[0][1][2]    )]
+              [ a( (*p)[a(3)][4][5] )]
+              [ a( (*p)[6][a(7)][8] )];
+}
+
+fn c() {
+  let v = b(&S[42]);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<array<i32, 9>, 9>, 9>, 50>;
+
+fn a(i : i32) -> i32 {
+  return i;
+}
+
+type S_X = array<u32, 1u>;
+
+fn b_S_X(p : S_X) -> i32 {
+  return S[p[0]][a(S[p[0]][0][1][2])][a(S[p[0]][a(3)][4][5])][a(S[p[0]][6][a(7)][8])];
+}
+
+fn c() {
+  let v = b_S_X(S_X(42));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessComplexTest, IndexingInPtrCall) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<array<i32, 9>, 9>, 9>, 50>;
+
+fn a(pre : i32, i : ptr<storage, i32>, post : i32) -> i32 {
+  return *i;
+}
+
+fn b(p : ptr<storage, array<array<array<i32, 9>, 9>, 9>>) -> i32 {
+  return a(10, &(*p)[ a( 20, &(*p)[0][1][2], 30 )]
+                    [ a( 40, &(*p)[3][4][5], 50 )]
+                    [ a( 60, &(*p)[6][7][8], 70 )], 80);
+}
+
+fn c() {
+  let v = b(&S[42]);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<array<i32, 9>, 9>, 9>, 50>;
+
+type S_X_X_X_X = array<u32, 4u>;
+
+fn a_S_X_X_X_X(pre : i32, i : S_X_X_X_X, post : i32) -> i32 {
+  return S[i[0]][i[0]][i[1]][i[2]];
+}
+
+type S_X = array<u32, 1u>;
+
+fn b_S_X(p : S_X) -> i32 {
+  return a_S_X_X_X_X(10, S_X_X_X_X(p[0u], u32(a_S_X_X_X_X(20, S_X_X_X_X(p[0u], 0, 1, 2), 30)), u32(a_S_X_X_X_X(40, S_X_X_X_X(p[0u], 3, 4, 5), 50)), u32(a_S_X_X_X_X(60, S_X_X_X_X(p[0u], 6, 7, 8), 70))), 80);
+}
+
+fn c() {
+  let v = b_S_X(S_X(42));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessComplexTest, IndexingDualPointers) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<i32, 9>, 9>, 50>;
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 9>, 9>, 50>;
+
+fn a(i : i32) -> i32 { return i; }
+
+fn b(s : ptr<storage, array<array<i32, 9>, 9>>,
+     u : ptr<uniform, array<array<vec4<i32>, 9>, 9>>) -> i32 {
+  return (*s) [ a( (*u)[0][1].x    )]
+              [ a( (*u)[a(3)][4].y )];
+}
+
+fn c() {
+  let v = b(&S[42], &U[24]);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<i32, 9>, 9>, 50>;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 9>, 9>, 50>;
+
+fn a(i : i32) -> i32 {
+  return i;
+}
+
+type S_X = array<u32, 1u>;
+
+type U_X = array<u32, 1u>;
+
+fn b_S_X_U_X(s : S_X, u : U_X) -> i32 {
+  return S[s[0]][a(U[u[0]][0][1].x)][a(U[u[0]][a(3)][4].y)];
+}
+
+fn c() {
+  let v = b_S_X_U_X(S_X(42), U_X(24));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace complex_tests
+
+}  // namespace
+}  // namespace tint::transform
diff --git a/src/tint/transform/module_scope_var_to_entry_point_param.cc b/src/tint/transform/module_scope_var_to_entry_point_param.cc
index 9c9c42f..9156205 100644
--- a/src/tint/transform/module_scope_var_to_entry_point_param.cc
+++ b/src/tint/transform/module_scope_var_to_entry_point_param.cc
@@ -146,7 +146,8 @@
                 attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kIgnoreAddressSpace));
 
                 auto* param_type = store_type();
-                if (auto* arr = ty->As<sem::Array>(); arr && arr->IsRuntimeSized()) {
+                if (auto* arr = ty->As<sem::Array>();
+                    arr && arr->Count()->Is<sem::RuntimeArrayCount>()) {
                     // Wrap runtime-sized arrays in structures, so that we can declare pointers to
                     // them. Ideally we'd just emit the array itself as a pointer, but this is not
                     // representable in Tint's AST.
diff --git a/src/tint/transform/multiplanar_external_texture.cc b/src/tint/transform/multiplanar_external_texture.cc
index 0667ae9..63de1f4 100644
--- a/src/tint/transform/multiplanar_external_texture.cc
+++ b/src/tint/transform/multiplanar_external_texture.cc
@@ -258,10 +258,11 @@
         utils::Vector ext_tex_params_member_list{
             b.Member("numPlanes", b.ty.u32()),
             b.Member("doYuvToRgbConversionOnly", b.ty.u32()),
-            b.Member("yuvToRgbConversionMatrix", b.ty.mat3x4(b.ty.f32())),
+            b.Member("yuvToRgbConversionMatrix", b.ty.mat3x4<f32>()),
             b.Member("gammaDecodeParams", b.ty.type_name("GammaTransferParams")),
             b.Member("gammaEncodeParams", b.ty.type_name("GammaTransferParams")),
-            b.Member("gamutConversionMatrix", b.ty.mat3x3(b.ty.f32()))};
+            b.Member("gamutConversionMatrix", b.ty.mat3x3<f32>()),
+            b.Member("rotationMatrix", b.ty.mat2x2<f32>())};
 
         params_struct_sym = b.Symbols().New("ExternalTextureParams");
 
@@ -314,22 +315,27 @@
         const ast::CallExpression* plane_1_call = nullptr;
         switch (call_type) {
             case sem::BuiltinType::kTextureSampleBaseClampToEdge:
+                stmts.Push(b.Decl(b.Let("modifiedCoords",
+                                        b.Add(b.Mul(b.Sub("coord", f32(0.5)),
+                                                    b.MemberAccessor("params", "rotationMatrix")),
+                                              f32(0.5)))));
+
                 stmts.Push(b.Decl(b.Let(
                     "plane0_dims",
                     b.Construct(b.ty.vec2<f32>(), b.Call("textureDimensions", "plane0", 0_a)))));
                 stmts.Push(
                     b.Decl(b.Let("plane0_half_texel", b.Div(b.vec2<f32>(0.5_a), "plane0_dims"))));
-                stmts.Push(
-                    b.Decl(b.Let("plane0_clamped", b.Call("clamp", "coord", "plane0_half_texel",
-                                                          b.Sub(1_a, "plane0_half_texel")))));
+                stmts.Push(b.Decl(
+                    b.Let("plane0_clamped", b.Call("clamp", "modifiedCoords", "plane0_half_texel",
+                                                   b.Sub(1_a, "plane0_half_texel")))));
                 stmts.Push(b.Decl(b.Let(
                     "plane1_dims",
                     b.Construct(b.ty.vec2<f32>(), b.Call("textureDimensions", "plane1", 0_a)))));
                 stmts.Push(
                     b.Decl(b.Let("plane1_half_texel", b.Div(b.vec2<f32>(0.5_a), "plane1_dims"))));
-                stmts.Push(
-                    b.Decl(b.Let("plane1_clamped", b.Call("clamp", "coord", "plane1_half_texel",
-                                                          b.Sub(1_a, "plane1_half_texel")))));
+                stmts.Push(b.Decl(
+                    b.Let("plane1_clamped", b.Call("clamp", "modifiedCoords", "plane1_half_texel",
+                                                   b.Sub(1_a, "plane1_half_texel")))));
 
                 // textureSampleLevel(plane0, smp, plane0_clamped, 0.0);
                 single_plane_call =
diff --git a/src/tint/transform/multiplanar_external_texture_test.cc b/src/tint/transform/multiplanar_external_texture_test.cc
index dbfbc8d..b220447 100644
--- a/src/tint/transform/multiplanar_external_texture_test.cc
+++ b/src/tint/transform/multiplanar_external_texture_test.cc
@@ -138,6 +138,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
@@ -193,6 +194,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
@@ -247,6 +249,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -265,12 +268,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -329,6 +333,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -343,12 +348,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -412,6 +418,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
@@ -504,6 +511,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
@@ -595,6 +603,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -613,12 +622,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -692,6 +702,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -706,12 +717,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -795,6 +807,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(4) var ext_tex_plane_1 : texture_2d<f32>;
@@ -831,12 +844,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -904,6 +918,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -918,12 +933,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -995,6 +1011,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1014,12 +1031,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -1086,6 +1104,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1100,12 +1119,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -1179,6 +1199,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(3) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1197,12 +1218,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -1281,6 +1303,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(3) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1304,12 +1327,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -1384,6 +1408,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1398,12 +1423,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -1483,6 +1509,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1497,12 +1524,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -1570,6 +1598,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 fn f(ext_tex : texture_2d<f32>, ext_tex_plane_1 : texture_2d<f32>, ext_tex_params : ExternalTextureParams) -> vec2<u32> {
@@ -1621,6 +1650,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1637,12 +1667,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
@@ -1715,6 +1746,7 @@
   gammaDecodeParams : GammaTransferParams,
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
+  rotationMatrix : mat2x2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1734,12 +1766,13 @@
 }
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
+  let modifiedCoords = (((coord - 0.5f) * params.rotationMatrix) + 0.5f);
   let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
   let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(coord, plane0_half_texel, (1 - plane0_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
   let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
   let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(coord, plane1_half_texel, (1 - plane1_half_texel));
+  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
   var color : vec3<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgb;
diff --git a/src/tint/transform/num_workgroups_from_uniform.cc b/src/tint/transform/num_workgroups_from_uniform.cc
index e6681ca..b6c93b2 100644
--- a/src/tint/transform/num_workgroups_from_uniform.cc
+++ b/src/tint/transform/num_workgroups_from_uniform.cc
@@ -107,7 +107,7 @@
                 // Capture the symbols that would be used to access this member, which
                 // we will replace later. We currently have no way to get from the
                 // parameter directly to the member accessor expressions that use it.
-                to_replace.insert({param->Declaration()->symbol, member->Declaration()->symbol});
+                to_replace.insert({param->Declaration()->symbol, member->Name()});
 
                 // Remove the struct member.
                 // The CanonicalizeEntryPointIO transform will have generated this
diff --git a/src/tint/transform/packed_vec3.cc b/src/tint/transform/packed_vec3.cc
index e947a53..f4ceb76 100644
--- a/src/tint/transform/packed_vec3.cc
+++ b/src/tint/transform/packed_vec3.cc
@@ -57,7 +57,7 @@
 
                                 // Apply the PackedVec3::Attribute to the member
                                 auto* member_decl = member->Declaration();
-                                auto name = ctx.Clone(member_decl->symbol);
+                                auto name = ctx.Clone(member->Name());
                                 auto* type = ctx.Clone(member_decl->type);
                                 utils::Vector<const ast::Attribute*, 4> attrs{
                                     b.ASTNodes().Create<Attribute>(b.ID(), b.AllocateNodeID()),
diff --git a/src/tint/transform/pad_structs.cc b/src/tint/transform/pad_structs.cc
index 4ceb39d..e8c21a7 100644
--- a/src/tint/transform/pad_structs.cc
+++ b/src/tint/transform/pad_structs.cc
@@ -84,7 +84,7 @@
                 // std140 structs should be padded out to 16 bytes.
                 size = utils::RoundUp(16u, size);
             } else if (auto* array_ty = ty->As<sem::Array>()) {
-                if (array_ty->IsRuntimeSized()) {
+                if (array_ty->Count()->Is<sem::RuntimeArrayCount>()) {
                     has_runtime_sized_array = true;
                 }
             }
diff --git a/src/tint/transform/robustness.cc b/src/tint/transform/robustness.cc
index 75d63f2..c9f6292 100644
--- a/src/tint/transform/robustness.cc
+++ b/src/tint/transform/robustness.cc
@@ -106,7 +106,7 @@
             },
             [&](const sem::Array* arr) -> const ast::Expression* {
                 const ast::Expression* max = nullptr;
-                if (arr->IsRuntimeSized()) {
+                if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
                     // Size is unknown until runtime.
                     // Must clamp, even if the index is constant.
                     auto* arr_ptr = b.AddressOf(ctx.Clone(expr->object));
diff --git a/src/tint/transform/single_entry_point.cc b/src/tint/transform/single_entry_point.cc
index 386631e..694cd2d 100644
--- a/src/tint/transform/single_entry_point.cc
+++ b/src/tint/transform/single_entry_point.cc
@@ -71,9 +71,12 @@
             [&](const ast::TypeDecl* ty) {
                 // Strip aliases that reference unused override declarations.
                 if (auto* arr = sem.Get(ty)->As<sem::Array>()) {
-                    for (auto* o : arr->TransitivelyReferencedOverrides()) {
-                        if (!referenced_vars.Contains(o)) {
-                            return;
+                    auto* refs = sem.TransitivelyReferencedOverrides(arr);
+                    if (refs) {
+                        for (auto* o : *refs) {
+                            if (!referenced_vars.Contains(o)) {
+                                return;
+                            }
                         }
                     }
                 }
diff --git a/src/tint/transform/spirv_atomic.cc b/src/tint/transform/spirv_atomic.cc
index 319975a..de3cdab 100644
--- a/src/tint/transform/spirv_atomic.cc
+++ b/src/tint/transform/spirv_atomic.cc
@@ -202,7 +202,7 @@
             [&](const sem::U32*) { return b.ty.atomic(CreateASTTypeFor(ctx, ty)); },
             [&](const sem::Struct* str) { return b.ty.type_name(Fork(str->Declaration()).name); },
             [&](const sem::Array* arr) -> const ast::Type* {
-                if (arr->IsRuntimeSized()) {
+                if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
                     return b.ty.array(AtomicTypeFor(arr->ElemType()));
                 }
                 auto count = arr->ConstantCount();
diff --git a/src/tint/transform/transform.cc b/src/tint/transform/transform.cc
index 5c8357c..cbfb90a 100644
--- a/src/tint/transform/transform.cc
+++ b/src/tint/transform/transform.cc
@@ -106,14 +106,14 @@
         if (!a->IsStrideImplicit()) {
             attrs.Push(ctx.dst->create<ast::StrideAttribute>(a->Stride()));
         }
-        if (a->IsRuntimeSized()) {
+        if (a->Count()->Is<sem::RuntimeArrayCount>()) {
             return ctx.dst->ty.array(el, nullptr, std::move(attrs));
         }
-        if (auto* override = std::get_if<sem::NamedOverrideArrayCount>(&a->Count())) {
+        if (auto* override = a->Count()->As<sem::NamedOverrideArrayCount>()) {
             auto* count = ctx.Clone(override->variable->Declaration());
             return ctx.dst->ty.array(el, count, std::move(attrs));
         }
-        if (auto* override = std::get_if<sem::UnnamedOverrideArrayCount>(&a->Count())) {
+        if (auto* override = a->Count()->As<sem::UnnamedOverrideArrayCount>()) {
             // If the array count is an unnamed (complex) override expression, then its not safe to
             // redeclare this type as we'd end up with two types that would not compare equal.
             // See crbug.com/tint/1764.
diff --git a/src/tint/transform/transform_test.cc b/src/tint/transform/transform_test.cc
index 4b8ad53..29a21e1 100644
--- a/src/tint/transform/transform_test.cc
+++ b/src/tint/transform/transform_test.cc
@@ -69,8 +69,8 @@
 
 TEST_F(CreateASTTypeForTest, ArrayImplicitStride) {
     auto* arr = create([](ProgramBuilder& b) {
-        return b.create<sem::Array>(b.create<sem::F32>(), sem::ConstantArrayCount{2u}, 4u, 4u, 32u,
-                                    32u);
+        return b.create<sem::Array>(b.create<sem::F32>(), b.create<sem::ConstantArrayCount>(2u), 4u,
+                                    4u, 32u, 32u);
     });
     ASSERT_TRUE(arr->Is<ast::Array>());
     ASSERT_TRUE(arr->As<ast::Array>()->type->Is<ast::F32>());
@@ -83,8 +83,8 @@
 
 TEST_F(CreateASTTypeForTest, ArrayNonImplicitStride) {
     auto* arr = create([](ProgramBuilder& b) {
-        return b.create<sem::Array>(b.create<sem::F32>(), sem::ConstantArrayCount{2u}, 4u, 4u, 64u,
-                                    32u);
+        return b.create<sem::Array>(b.create<sem::F32>(), b.create<sem::ConstantArrayCount>(2u), 4u,
+                                    4u, 64u, 32u);
     });
     ASSERT_TRUE(arr->Is<ast::Array>());
     ASSERT_TRUE(arr->As<ast::Array>()->type->Is<ast::F32>());
@@ -122,8 +122,8 @@
 TEST_F(CreateASTTypeForTest, Struct) {
     auto* str = create([](ProgramBuilder& b) {
         auto* decl = b.Structure("S", {});
-        return b.create<sem::Struct>(decl, decl->name, sem::StructMemberList{}, 4u /* align */,
-                                     4u /* size */, 4u /* size_no_padding */);
+        return b.create<sem::Struct>(decl, decl->source, decl->name, sem::StructMemberList{},
+                                     4u /* align */, 4u /* size */, 4u /* size_no_padding */);
     });
     ASSERT_TRUE(str->Is<ast::TypeName>());
     EXPECT_EQ(ast_type_builder.Symbols().NameFor(str->As<ast::TypeName>()->name), "S");
diff --git a/src/tint/transform/truncate_interstage_variables.cc b/src/tint/transform/truncate_interstage_variables.cc
index 30237bc..bbb27fd 100644
--- a/src/tint/transform/truncate_interstage_variables.cc
+++ b/src/tint/transform/truncate_interstage_variables.cc
@@ -131,8 +131,7 @@
                     }
 
                     truncated_members.Push(ctx.Clone(member->Declaration()));
-                    initializer_exprs.Push(
-                        b.MemberAccessor("io", ctx.Clone(member->Declaration()->symbol)));
+                    initializer_exprs.Push(b.MemberAccessor("io", ctx.Clone(member->Name())));
                 }
 
                 // Create the new shader io struct.
diff --git a/src/tint/transform/unshadow.h b/src/tint/transform/unshadow.h
index 8ebf105..9030006 100644
--- a/src/tint/transform/unshadow.h
+++ b/src/tint/transform/unshadow.h
@@ -19,8 +19,7 @@
 
 namespace tint::transform {
 
-/// Unshadow is a Transform that renames any variables that shadow another
-/// variable.
+/// Unshadow is a Transform that renames any variables that shadow another variable.
 class Unshadow final : public Castable<Unshadow, Transform> {
   public:
     /// Constructor
diff --git a/src/tint/utils/block_allocator.h b/src/tint/utils/block_allocator.h
index 91a8a53..e75a0c6 100644
--- a/src/tint/utils/block_allocator.h
+++ b/src/tint/utils/block_allocator.h
@@ -39,6 +39,7 @@
         static constexpr size_t kMax = 32;
         std::array<T*, kMax> ptrs;
         Pointers* next;
+        Pointers* prev;
     };
 
     /// Block is linked list of memory blocks.
@@ -55,7 +56,7 @@
     class TView;
 
     /// An iterator for the objects owned by the BlockAllocator.
-    template <bool IS_CONST>
+    template <bool IS_CONST, bool FORWARD>
     class TIterator {
         using PointerTy = std::conditional_t<IS_CONST, const T*, T*>;
 
@@ -72,15 +73,24 @@
         /// @returns true if this iterator is not equal to other
         bool operator!=(const TIterator& other) const { return !(*this == other); }
 
-        /// Advances the iterator
+        /// Progress the iterator forward one element
         /// @returns this iterator
         TIterator& operator++() {
-            if (ptrs != nullptr) {
-                ++idx;
-                if (idx == Pointers::kMax) {
-                    idx = 0;
-                    ptrs = ptrs->next;
-                }
+            if (FORWARD) {
+                ProgressForward();
+            } else {
+                ProgressBackwards();
+            }
+            return *this;
+        }
+
+        /// Progress the iterator backwards one element
+        /// @returns this iterator
+        TIterator& operator--() {
+            if (FORWARD) {
+                ProgressBackwards();
+            } else {
+                ProgressForward();
             }
             return *this;
         }
@@ -92,6 +102,27 @@
         friend TView<IS_CONST>;  // Keep internal iterator impl private.
         explicit TIterator(const Pointers* p, size_t i) : ptrs(p), idx(i) {}
 
+        /// Progresses the iterator forwards
+        void ProgressForward() {
+            if (ptrs != nullptr) {
+                ++idx;
+                if (idx == Pointers::kMax) {
+                    idx = 0;
+                    ptrs = ptrs->next;
+                }
+            }
+        }
+        /// Progresses the iterator backwards
+        void ProgressBackwards() {
+            if (ptrs != nullptr) {
+                if (idx == 0) {
+                    idx = Pointers::kMax - 1;
+                    ptrs = ptrs->prev;
+                }
+                --idx;
+            }
+        }
+
         const Pointers* ptrs;
         size_t idx;
     };
@@ -102,16 +133,25 @@
     class TView {
       public:
         /// @returns an iterator to the beginning of the view
-        TIterator<IS_CONST> begin() const {
-            return TIterator<IS_CONST>{allocator_->data.pointers.root, 0};
+        TIterator<IS_CONST, true> begin() const {
+            return TIterator<IS_CONST, true>{allocator_->data.pointers.root, 0};
         }
 
         /// @returns an iterator to the end of the view
-        TIterator<IS_CONST> end() const {
+        TIterator<IS_CONST, true> end() const {
             return allocator_->data.pointers.current_index >= Pointers::kMax
-                       ? TIterator<IS_CONST>(nullptr, 0)
-                       : TIterator<IS_CONST>(allocator_->data.pointers.current,
-                                             allocator_->data.pointers.current_index);
+                       ? TIterator<IS_CONST, true>{nullptr, 0}
+                       : TIterator<IS_CONST, true>{allocator_->data.pointers.current,
+                                                   allocator_->data.pointers.current_index};
+        }
+
+        /// @returns an iterator to the beginning of the view
+        TIterator<IS_CONST, false> rbegin() const { return TIterator<IS_CONST, false>{nullptr, 0}; }
+
+        /// @returns an iterator to the end of the view
+        TIterator<IS_CONST, false> rend() const {
+            return TIterator<IS_CONST, false>{allocator_->data.pointers.current,
+                                              allocator_->data.pointers.current_index};
         }
 
       private:
@@ -121,11 +161,17 @@
     };
 
   public:
-    /// An iterator type over the objects of the BlockAllocator
-    using Iterator = TIterator<false>;
+    /// A forward-iterator type over the objects of the BlockAllocator
+    using Iterator = TIterator</* const */ false, /* forward */ true>;
 
-    /// An immutable iterator type over the objects of the BlockAllocator
-    using ConstIterator = TIterator<true>;
+    /// An immutable forward-iterator type over the objects of the BlockAllocator
+    using ConstIterator = TIterator</* const */ true, /* forward */ true>;
+
+    /// A reverse-iterator type over the objects of the BlockAllocator
+    using ReverseIterator = TIterator</* const */ false, /* forward */ false>;
+
+    /// An immutable reverse-iterator type over the objects of the BlockAllocator
+    using ReverseConstIterator = TIterator</* const */ true, /* forward */ false>;
 
     /// View provides begin() and end() methods for looping over the objects owned by the
     /// BlockAllocator.
@@ -248,6 +294,7 @@
                 return;  // out of memory
             }
             pointers.current->next = nullptr;
+            pointers.current->prev = prev_pointers;
             pointers.current_index = 0;
 
             if (prev_pointers) {
diff --git a/src/tint/utils/unique_allocator.h b/src/tint/utils/unique_allocator.h
index 1297701..7ba5909 100644
--- a/src/tint/utils/unique_allocator.h
+++ b/src/tint/utils/unique_allocator.h
@@ -28,6 +28,9 @@
 template <typename T, typename HASH = std::hash<T>, typename EQUAL = std::equal_to<T>>
 class UniqueAllocator {
   public:
+    /// Iterator is the type returned by begin() and end()
+    using Iterator = typename BlockAllocator<T>::ConstIterator;
+
     /// @param args the arguments used to construct the object.
     /// @return a pointer to an instance of `T` with the provided arguments.
     ///         If an existing instance of `T` has been constructed, then the same
@@ -49,7 +52,36 @@
         return ptr;
     }
 
-  protected:
+    /// @param args the arguments used to create the temporary used for the search.
+    /// @return a pointer to an instance of `T` with the provided arguments, or nullptr if the item
+    ///         was not found.
+    template <typename TYPE = T, typename... ARGS>
+    TYPE* Find(ARGS&&... args) const {
+        // Create a temporary T instance on the stack so that we can hash it, and
+        // use it for equality lookup for the std::unordered_set.
+        TYPE key{args...};
+        auto hash = Hasher{}(key);
+        auto it = items.find(Entry{hash, &key});
+        if (it != items.end()) {
+            return static_cast<TYPE*>(it->ptr);
+        }
+        return nullptr;
+    }
+
+    /// Wrap sets this allocator to the objects created with the content of `inner`.
+    /// The allocator after Wrap is intended to temporarily extend the objects
+    /// of an existing immutable UniqueAllocator.
+    /// As the copied objects are owned by `inner`, `inner` must not be destructed
+    /// or assigned while using this allocator.
+    /// @param o the immutable UniqueAlllocator to extend
+    void Wrap(const UniqueAllocator<T, HASH, EQUAL>& o) { items = o.items; }
+
+    /// @returns an iterator to the beginning of the types
+    Iterator begin() const { return allocator.Objects().begin(); }
+    /// @returns an iterator to the end of the types
+    Iterator end() const { return allocator.Objects().end(); }
+
+  private:
     /// The hash function
     using Hasher = HASH;
     /// The equality function
@@ -59,7 +91,7 @@
     struct Entry {
         /// The pre-calculated hash of the entry
         size_t hash;
-        /// Tge pointer to the unique object
+        /// The pointer to the unique object
         T* ptr;
     };
     /// Comparator is the hashing and equality function used by the unordered_set
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 86ba657..c9ca429 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -54,6 +54,7 @@
 #include "src/tint/transform/combine_samplers.h"
 #include "src/tint/transform/decompose_memory_access.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"
 #include "src/tint/transform/expand_compound_assignment.h"
 #include "src/tint/transform/manager.h"
@@ -209,7 +210,8 @@
     manager.Add<transform::Renamer>();
     data.Add<transform::Renamer::Config>(transform::Renamer::Target::kGlslKeywords,
                                          /* preserve_unicode */ false);
-    manager.Add<transform::Unshadow>();
+    manager.Add<transform::Unshadow>();  // Must come before DirectVariableAccess
+    manager.Add<transform::DirectVariableAccess>();
 
     if (!options.disable_workgroup_init) {
         // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
@@ -290,7 +292,7 @@
             auto* sem = builder_.Sem().Get(str);
             bool has_rt_arr = false;
             if (auto* arr = sem->Members().back()->Type()->As<sem::Array>()) {
-                has_rt_arr = arr->IsRuntimeSized();
+                has_rt_arr = arr->Count()->Is<sem::RuntimeArrayCount>();
             }
             bool is_block =
                 ast::HasAttribute<transform::AddBlockAttribute::BlockAttribute>(str->attributes);
@@ -2832,7 +2834,7 @@
         const sem::Type* base_type = ary;
         std::vector<uint32_t> sizes;
         while (auto* arr = base_type->As<sem::Array>()) {
-            if (arr->IsRuntimeSized()) {
+            if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
                 sizes.push_back(0);
             } else {
                 auto count = arr->ConstantCount();
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 1e786da..0c3856a 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -54,6 +54,7 @@
 #include "src/tint/transform/canonicalize_entry_point_io.h"
 #include "src/tint/transform/decompose_memory_access.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"
 #include "src/tint/transform/expand_compound_assignment.h"
 #include "src/tint/transform/localize_struct_array_assignment.h"
@@ -194,7 +195,9 @@
     }
     manager.Add<transform::MultiplanarExternalTexture>();
 
-    manager.Add<transform::Unshadow>();
+    manager.Add<transform::Unshadow>();  // Must come before DirectVariableAccess
+
+    manager.Add<transform::DirectVariableAccess>();
 
     // LocalizeStructArrayAssignment must come after:
     // * SimplifyPointers, because it assumes assignment to arrays in structs are
@@ -291,6 +294,7 @@
                                   utils::Vector{
                                       ast::Extension::kChromiumDisableUniformityAnalysis,
                                       ast::Extension::kChromiumExperimentalDp4A,
+                                      ast::Extension::kChromiumExperimentalFullPtrParameters,
                                       ast::Extension::kChromiumExperimentalPushConstant,
                                       ast::Extension::kF16,
                                   })) {
@@ -3920,7 +3924,7 @@
             const sem::Type* base_type = ary;
             std::vector<uint32_t> sizes;
             while (auto* arr = base_type->As<sem::Array>()) {
-                if (arr->IsRuntimeSized()) {
+                if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
                     TINT_ICE(Writer, diagnostics_)
                         << "runtime arrays may only exist in storage buffers, which should have "
                            "been transformed into a ByteAddressBuffer";
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 10efd59..c7354f0 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -266,6 +266,7 @@
     if (!CheckSupportedExtensions("MSL", program_->AST(), diagnostics_,
                                   utils::Vector{
                                       ast::Extension::kChromiumDisableUniformityAnalysis,
+                                      ast::Extension::kChromiumExperimentalFullPtrParameters,
                                       ast::Extension::kChromiumExperimentalPushConstant,
                                       ast::Extension::kF16,
                                   })) {
@@ -2536,7 +2537,7 @@
                 return false;
             }
             out << ", ";
-            if (arr->IsRuntimeSized()) {
+            if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
                 out << "1";
             } else {
                 auto count = arr->ConstantCount();
@@ -3164,7 +3165,7 @@
                     << "arrays with explicit strides should not exist past the SPIR-V reader";
                 return SizeAndAlign{};
             }
-            if (arr->IsRuntimeSized()) {
+            if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
                 return SizeAndAlign{arr->Stride(), arr->Align()};
             }
             if (auto count = arr->ConstantCount()) {
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index c5e4a38..f2b8f85 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -260,6 +260,7 @@
                                   utils::Vector{
                                       ast::Extension::kChromiumDisableUniformityAnalysis,
                                       ast::Extension::kChromiumExperimentalDp4A,
+                                      ast::Extension::kChromiumExperimentalFullPtrParameters,
                                       ast::Extension::kChromiumExperimentalPushConstant,
                                       ast::Extension::kF16,
                                   })) {
@@ -3838,7 +3839,7 @@
     }
 
     auto result_id = std::get<uint32_t>(result);
-    if (arr->IsRuntimeSized()) {
+    if (arr->Count()->Is<sem::RuntimeArrayCount>()) {
         push_type(spv::Op::OpTypeRuntimeArray, {result, Operand(elem_type)});
     } else {
         auto count = arr->ConstantCount();
diff --git a/src/tint/writer/spirv/generator_impl.cc b/src/tint/writer/spirv/generator_impl.cc
index 18c7511..b6bb0ff 100644
--- a/src/tint/writer/spirv/generator_impl.cc
+++ b/src/tint/writer/spirv/generator_impl.cc
@@ -22,6 +22,7 @@
 #include "src/tint/transform/builtin_polyfill.h"
 #include "src/tint/transform/canonicalize_entry_point_io.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"
 #include "src/tint/transform/expand_compound_assignment.h"
 #include "src/tint/transform/for_loop_to_loop.h"
@@ -77,12 +78,21 @@
     }
     manager.Add<transform::MultiplanarExternalTexture>();
 
-    manager.Add<transform::Unshadow>();
+    manager.Add<transform::Unshadow>();  // Must come before DirectVariableAccess
     bool disable_workgroup_init_in_sanitizer =
         options.disable_workgroup_init || options.use_zero_initialize_workgroup_memory_extension;
     if (!disable_workgroup_init_in_sanitizer) {
         manager.Add<transform::ZeroInitWorkgroupMemory>();
     }
+
+    {
+        transform::DirectVariableAccess::Options opts;
+        opts.transform_private = true;
+        opts.transform_function = true;
+        data.Add<transform::DirectVariableAccess::Config>(opts);
+        manager.Add<transform::DirectVariableAccess>();
+    }
+
     manager.Add<transform::RemoveUnreachableStatements>();
     manager.Add<transform::PromoteSideEffectsToDecl>();
     manager.Add<transform::SimplifyPointers>();  // Required for arrayLength()
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index 6327c6c..cfd512b 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -606,8 +606,8 @@
     increment_indent();
     uint32_t offset = 0;
     for (auto* mem : str->members) {
-        // TODO(crbug.com/tint/798) move the @offset attribute handling to the
-        // transform::Wgsl sanitizer.
+        // TODO(crbug.com/tint/798) move the @offset attribute handling to the transform::Wgsl
+        // sanitizer.
         if (auto* mem_sem = program_->Sem().Get(mem)) {
             offset = utils::RoundUp(mem_sem->Align(), offset);
             if (uint32_t padding = mem_sem->Offset() - offset) {
@@ -623,7 +623,14 @@
         utils::Vector<const ast::Attribute*, 4> attributes_sanitized;
         attributes_sanitized.Reserve(mem->attributes.Length());
         for (auto* attr : mem->attributes) {
-            if (!attr->Is<ast::StructMemberOffsetAttribute>()) {
+            if (attr->Is<ast::StructMemberOffsetAttribute>()) {
+                auto l = line();
+                l << "/* ";
+                if (!EmitAttributes(l, utils::Vector{attr})) {
+                    return false;
+                }
+                l << " */";
+            } else {
                 attributes_sanitized.Push(attr);
             }
         }
@@ -792,6 +799,14 @@
                 out << ")";
                 return true;
             },
+            [&](const ast::StructMemberOffsetAttribute* offset) {
+                out << "offset(";
+                if (!EmitExpression(out, offset->expr)) {
+                    return false;
+                }
+                out << ")";
+                return true;
+            },
             [&](const ast::StructMemberSizeAttribute* size) {
                 out << "size(";
                 if (!EmitExpression(out, size->expr)) {
diff --git a/src/tint/writer/wgsl/generator_impl_type_test.cc b/src/tint/writer/wgsl/generator_impl_type_test.cc
index 13c8411..50bc7df 100644
--- a/src/tint/writer/wgsl/generator_impl_type_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_type_test.cc
@@ -188,9 +188,11 @@
     EXPECT_EQ(gen.result(), R"(struct S {
   @size(8)
   padding : u32,
+  /* @offset(8) */
   a : i32,
   @size(4)
   padding_1 : u32,
+  /* @offset(16) */
   b : f32,
 }
 )");
@@ -209,9 +211,11 @@
     EXPECT_EQ(gen.result(), R"(struct S {
   @size(8)
   padding : u32,
+  /* @offset(8) */
   tint_0_padding : i32,
   @size(4)
   padding_1 : u32,
+  /* @offset(16) */
   tint_2_padding : f32,
 }
 )");